From e54fda6b34dc000648d955c27afa79cf21e21988 Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Wed, 15 Jul 2015 18:26:39 +0800 Subject: [PATCH 01/11] Introduce Electron custom statusItem view. Reimplement tray functions on OS X by using custom statusItem view. --- atom/browser/ui/tray_icon_cocoa.h | 7 +- atom/browser/ui/tray_icon_cocoa.mm | 166 +++++++++++++++++++++++------ 2 files changed, 135 insertions(+), 38 deletions(-) diff --git a/atom/browser/ui/tray_icon_cocoa.h b/atom/browser/ui/tray_icon_cocoa.h index 5723cb6b2196..d4cfd431e466 100644 --- a/atom/browser/ui/tray_icon_cocoa.h +++ b/atom/browser/ui/tray_icon_cocoa.h @@ -13,7 +13,7 @@ #include "base/mac/scoped_nsobject.h" @class AtomMenuController; -@class StatusItemController; +@class StatusItemView; namespace atom { @@ -30,9 +30,8 @@ class TrayIconCocoa : public TrayIcon { void SetContextMenu(ui::SimpleMenuModel* menu_model) override; private: - base::scoped_nsobject item_; - - base::scoped_nsobject controller_; + // Atom custom view for NSStatusItem. + base::scoped_nsobject status_item_view_; // Status menu shown when right-clicking the system icon. base::scoped_nsobject menu_; diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index f989b9b580e2..11efbe0aee90 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -9,35 +9,142 @@ #include "ui/gfx/image/image.h" #include "ui/gfx/screen.h" -@interface StatusItemController : NSObject { + +const CGFloat kStatusItemLength = 26; +const CGFloat kMargin = 3; + +@interface StatusItemView : NSView { atom::TrayIconCocoa* trayIcon_; // weak + AtomMenuController* menu_controller_; // weak + BOOL isHighlightEnable_; + BOOL inMouseEventSequence_; + base::scoped_nsobject image_; + base::scoped_nsobject alternateImage_; + base::scoped_nsobject title_; + base::scoped_nsobject statusItem_; } -- (id)initWithIcon:(atom::TrayIconCocoa*)icon; -- (void)handleClick:(id)sender; -- (void)handleDoubleClick:(id)sender; -@end // @interface StatusItemController +@end // @interface StatusItemView -@implementation StatusItemController +@implementation StatusItemView - (id)initWithIcon:(atom::TrayIconCocoa*)icon { trayIcon_ = icon; + isHighlightEnable_ = YES; + statusItem_.reset([[[NSStatusBar systemStatusBar] statusItemWithLength: + NSVariableStatusItemLength] retain]); + NSRect frame = NSMakeRect(0, 0, 26, [[statusItem_ statusBar] thickness]); + if ((self = [super initWithFrame:frame])) { + [statusItem_ setView:self]; + } return self; } -- (void)handleClick:(id)sender { - // Get the frame of the NSStatusItem. - NSRect frame = [NSApp currentEvent].window.frame; - gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame)); - // Flip coordinates to gfx (0,0 in top-left corner) using current screen. - NSScreen* screen = [[NSScreen screens] objectAtIndex:0]; - bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame)); - - trayIcon_->NotifyClicked(bounds); +- (void)removeItem { + [[NSStatusBar systemStatusBar] removeStatusItem:statusItem_]; + statusItem_.reset(); } -- (void)handleDoubleClick:(id)sender { - trayIcon_->NotifyDoubleClicked(); +- (void)drawRect:(NSRect)dirtyRect { + BOOL highlight = [self shouldHighlight]; + [statusItem_ drawStatusBarBackgroundInRect:[self bounds] + withHighlight:highlight]; + NSRect icon_frame = NSMakeRect(0, + 0, + kStatusItemLength, + [[statusItem_ statusBar] thickness]); + NSRect icon_draw_rect = NSInsetRect(icon_frame, kMargin, kMargin); + if (highlight && alternateImage_) { + [alternateImage_ drawInRect:icon_draw_rect + fromRect:NSZeroRect + operation:NSCompositeSourceOver + fraction:1]; + } else { + [image_ drawInRect:icon_draw_rect + fromRect:NSZeroRect + operation:NSCompositeSourceOver + fraction:1]; + } + if (title_) { + NSAttributedString* attributes = + [[NSAttributedString alloc] initWithString:title_ + attributes:[self titleAttributes]]; + CGFloat title_width = [attributes size].width; + NSRect title_rect = NSMakeRect(kStatusItemLength, + 0, + title_width + kStatusItemLength, + [[statusItem_ statusBar] thickness]); + [title_ drawInRect:title_rect + withAttributes:[self titleAttributes]]; + [statusItem_ setLength:title_width + kStatusItemLength]; + } +} + +- (NSDictionary *)titleAttributes { + NSFont* font = [NSFont menuBarFontOfSize:0]; + NSColor* foregroundColor = [NSColor blackColor]; + + return [NSDictionary dictionaryWithObjectsAndKeys: + font, NSFontAttributeName, + foregroundColor, NSForegroundColorAttributeName, + nil]; +} + +- (void)setImage:(NSImage*)image { + image_.reset([image copy]); +} + +- (void)setAlternateImage:(NSImage*)image { + alternateImage_.reset([image copy]); +} + +- (void)setHighlight:(BOOL)highlight { + isHighlightEnable_ = highlight; +} + +-(void)setTitle:(NSString*) title { + //title_= [title retain]; + title_.reset([title copy]); +} + +- (void)setMenuController:(AtomMenuController*)menu { + menu_controller_ = menu; +} + +-(void)mouseDown:(NSEvent *)event { + inMouseEventSequence_ = YES; + [self setNeedsDisplay:YES]; +} + +-(void)mouseUp:(NSEvent *)event { + if (!inMouseEventSequence_) { + // If the menu is showing, when user clicked the tray icon, the `mouseDown` + // event will be dissmissed, we need to close the menu at this time. + [self setNeedsDisplay:YES]; + return; + } + inMouseEventSequence_ = NO; + if (event.clickCount == 1) { + if (menu_controller_) { + [statusItem_ popUpStatusItemMenu:[menu_controller_ menu]]; + } + + NSRect frame = event.window.frame; + gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame)); + NSScreen* screen = [[NSScreen screens] objectAtIndex:0]; + bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame)); + trayIcon_->NotifyClicked(bounds); + } + + if (event.clickCount == 2 && !menu_controller_) { + trayIcon_->NotifyDoubleClicked(); + } + [self setNeedsDisplay:YES]; +} + +-(BOOL) shouldHighlight { + BOOL is_menu_open = [menu_controller_ isMenuOpen]; + return isHighlightEnable_ && (inMouseEventSequence_ || is_menu_open); } @end @@ -45,45 +152,36 @@ namespace atom { TrayIconCocoa::TrayIconCocoa() { - controller_.reset([[StatusItemController alloc] initWithIcon:this]); - - item_.reset([[[NSStatusBar systemStatusBar] - statusItemWithLength:NSVariableStatusItemLength] retain]); - [item_ setEnabled:YES]; - [item_ setTarget:controller_]; - [item_ setAction:@selector(handleClick:)]; - [item_ setDoubleAction:@selector(handleDoubleClick:)]; - [item_ setHighlightMode:YES]; + status_item_view_.reset([[StatusItemView alloc] initWithIcon:this]); } TrayIconCocoa::~TrayIconCocoa() { - // Remove the status item from the status bar. - [[NSStatusBar systemStatusBar] removeStatusItem:item_]; + [status_item_view_ removeItem]; } void TrayIconCocoa::SetImage(const gfx::Image& image) { - [item_ setImage:image.AsNSImage()]; + [status_item_view_ setImage:image.AsNSImage()]; } void TrayIconCocoa::SetPressedImage(const gfx::Image& image) { - [item_ setAlternateImage:image.AsNSImage()]; + [status_item_view_ setAlternateImage:image.AsNSImage()]; } void TrayIconCocoa::SetToolTip(const std::string& tool_tip) { - [item_ setToolTip:base::SysUTF8ToNSString(tool_tip)]; + [status_item_view_ setToolTip:base::SysUTF8ToNSString(tool_tip)]; } void TrayIconCocoa::SetTitle(const std::string& title) { - [item_ setTitle:base::SysUTF8ToNSString(title)]; + [status_item_view_ setTitle:base::SysUTF8ToNSString(title)]; } void TrayIconCocoa::SetHighlightMode(bool highlight) { - [item_ setHighlightMode:highlight]; + [status_item_view_ setHighlight:highlight]; } void TrayIconCocoa::SetContextMenu(ui::SimpleMenuModel* menu_model) { menu_.reset([[AtomMenuController alloc] initWithModel:menu_model]); - [item_ setMenu:[menu_ menu]]; + [status_item_view_ setMenuController:menu_.get()]; } // static From cca4f4abd588d7fe3970d3cca6d7f0f5d6b86282 Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Wed, 15 Jul 2015 19:23:12 +0800 Subject: [PATCH 02/11] Implement 'right-clicked' tray event on OS X. --- atom/browser/api/atom_api_tray.cc | 4 ++++ atom/browser/api/atom_api_tray.h | 1 + atom/browser/ui/tray_icon.cc | 4 ++++ atom/browser/ui/tray_icon.h | 1 + atom/browser/ui/tray_icon_cocoa.mm | 19 +++++++++++++------ atom/browser/ui/tray_icon_observer.h | 1 + 6 files changed, 24 insertions(+), 6 deletions(-) diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc index 649967a2b1eb..7c1bfdfac95a 100644 --- a/atom/browser/api/atom_api_tray.cc +++ b/atom/browser/api/atom_api_tray.cc @@ -60,6 +60,10 @@ void Tray::OnBalloonClosed() { Emit("balloon-closed"); } +void Tray::OnRightClicked(const gfx::Rect& bounds) { + Emit("right-clicked", bounds); +} + bool Tray::IsDestroyed() const { return !tray_icon_; } diff --git a/atom/browser/api/atom_api_tray.h b/atom/browser/api/atom_api_tray.h index 1a4a498d16b9..366a847ac602 100644 --- a/atom/browser/api/atom_api_tray.h +++ b/atom/browser/api/atom_api_tray.h @@ -46,6 +46,7 @@ class Tray : public mate::EventEmitter, void OnBalloonShow() override; void OnBalloonClicked() override; void OnBalloonClosed() override; + void OnRightClicked(const gfx::Rect&) override; // mate::Wrappable: bool IsDestroyed() const override; diff --git a/atom/browser/ui/tray_icon.cc b/atom/browser/ui/tray_icon.cc index a3878f718a62..4f801faf862e 100644 --- a/atom/browser/ui/tray_icon.cc +++ b/atom/browser/ui/tray_icon.cc @@ -46,4 +46,8 @@ void TrayIcon::NotifyBalloonClosed() { FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnBalloonClosed()); } +void TrayIcon::NotifyRightClicked(const gfx::Rect& bounds) { + FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnRightClicked(bounds)); +} + } // namespace atom diff --git a/atom/browser/ui/tray_icon.h b/atom/browser/ui/tray_icon.h index 7dc67da1bac4..4e097e0ac84a 100644 --- a/atom/browser/ui/tray_icon.h +++ b/atom/browser/ui/tray_icon.h @@ -56,6 +56,7 @@ class TrayIcon { void NotifyBalloonShow(); void NotifyBalloonClicked(); void NotifyBalloonClosed(); + void NotifyRightClicked(const gfx::Rect& bounds = gfx::Rect()); protected: TrayIcon(); diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index 11efbe0aee90..2d0acdd6aaf4 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -75,7 +75,7 @@ const CGFloat kMargin = 3; title_width + kStatusItemLength, [[statusItem_ statusBar] thickness]); [title_ drawInRect:title_rect - withAttributes:[self titleAttributes]]; + withAttributes:[self titleAttributes]]; [statusItem_ setLength:title_width + kStatusItemLength]; } } @@ -129,11 +129,7 @@ const CGFloat kMargin = 3; [statusItem_ popUpStatusItemMenu:[menu_controller_ menu]]; } - NSRect frame = event.window.frame; - gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame)); - NSScreen* screen = [[NSScreen screens] objectAtIndex:0]; - bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame)); - trayIcon_->NotifyClicked(bounds); + trayIcon_->NotifyClicked([self getBoundsFromEvent:event]); } if (event.clickCount == 2 && !menu_controller_) { @@ -142,11 +138,22 @@ const CGFloat kMargin = 3; [self setNeedsDisplay:YES]; } +- (void)rightMouseUp:(NSEvent*)event { + trayIcon_->NotifyRightClicked([self getBoundsFromEvent:event]); +} + -(BOOL) shouldHighlight { BOOL is_menu_open = [menu_controller_ isMenuOpen]; return isHighlightEnable_ && (inMouseEventSequence_ || is_menu_open); } +-(gfx::Rect) getBoundsFromEvent:(NSEvent*)event { + NSRect frame = event.window.frame; + gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame)); + NSScreen* screen = [[NSScreen screens] objectAtIndex:0]; + bounds.set_y(NSHeight([screen frame]) - NSMaxY(frame)); + return bounds; +} @end namespace atom { diff --git a/atom/browser/ui/tray_icon_observer.h b/atom/browser/ui/tray_icon_observer.h index 3a34888b5318..061115efdc64 100644 --- a/atom/browser/ui/tray_icon_observer.h +++ b/atom/browser/ui/tray_icon_observer.h @@ -18,6 +18,7 @@ class TrayIconObserver { virtual void OnBalloonShow() {} virtual void OnBalloonClicked() {} virtual void OnBalloonClosed() {} + virtual void OnRightClicked(const gfx::Rect&) {} protected: virtual ~TrayIconObserver() {} From 5ad3fff6a08c1df45e3fb601ecbf202b776ab82b Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Wed, 15 Jul 2015 20:03:05 +0800 Subject: [PATCH 03/11] Implement 'right-clicked' tray event on Windows. --- atom/browser/ui/win/notify_icon.cc | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/atom/browser/ui/win/notify_icon.cc b/atom/browser/ui/win/notify_icon.cc index 955a047fe1f5..64329a63e5e4 100644 --- a/atom/browser/ui/win/notify_icon.cc +++ b/atom/browser/ui/win/notify_icon.cc @@ -47,21 +47,22 @@ NotifyIcon::~NotifyIcon() { void NotifyIcon::HandleClickEvent(const gfx::Point& cursor_pos, bool left_mouse_click) { + NOTIFYICONIDENTIFIER icon_id; + memset(&icon_id, 0, sizeof(NOTIFYICONIDENTIFIER)); + icon_id.uID = icon_id_; + icon_id.hWnd = window_; + icon_id.cbSize = sizeof(NOTIFYICONIDENTIFIER); + RECT rect = { 0 }; + Shell_NotifyIconGetRect(&icon_id, &rect); + // Pass to the observer if appropriate. if (left_mouse_click) { - NOTIFYICONIDENTIFIER icon_id; - memset(&icon_id, 0, sizeof(NOTIFYICONIDENTIFIER)); - icon_id.uID = icon_id_; - icon_id.hWnd = window_; - icon_id.cbSize = sizeof(NOTIFYICONIDENTIFIER); - - RECT rect = { 0 }; - Shell_NotifyIconGetRect(&icon_id, &rect); - NotifyClicked(gfx::Rect(rect)); return; } + NotifyRightClicked(gfx::Rect(rect)); + if (!menu_model_) return; From 38c33d69ae2b1c7d6bcdd86069933d848d44513b Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Wed, 15 Jul 2015 20:13:59 +0800 Subject: [PATCH 04/11] :memo: 'right-clicked' doc. --- docs/api/tray.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/api/tray.md b/docs/api/tray.md index 25c9cb451b1d..6d1bbb75ec6c 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -25,7 +25,6 @@ app.on('ready', function(){ __Platform limitations:__ -* On OS X `clicked` event will be ignored if the tray icon has context menu. * On Linux app indicator will be used if it is supported, otherwise `GtkStatusIcon` will be used instead. * App indicator will only be showed when it has context menu. @@ -57,6 +56,20 @@ Emitted when the tray icon is clicked. __Note:__ The `bounds` payload is only implemented on OS X and Windows 7 or newer. +### Event: 'right-clicked' + +* `event` +* `bounds` Object - the bounds of tray icon + * `x` Integer + * `y` Integer + * `width` Integer + * `height` Integer + +Emitted when the tray icon is right clicked. + +__Note:__ This is only implemented on OS X and Windows. On Windows, this event +will be emitted if the tray icon has context menu. + ### Event: 'double-clicked' Emitted when the tray icon is double clicked. From 002eb1a3266c2540b5c0d90752819bffc8cae52c Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Thu, 16 Jul 2015 10:49:55 +0800 Subject: [PATCH 05/11] Simplify code logic and fix object-c code style. --- atom/browser/ui/tray_icon_cocoa.mm | 75 ++++++++++++++++++------------ 1 file changed, 45 insertions(+), 30 deletions(-) diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index 2d0acdd6aaf4..514631c21065 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -9,13 +9,12 @@ #include "ui/gfx/image/image.h" #include "ui/gfx/screen.h" - const CGFloat kStatusItemLength = 26; const CGFloat kMargin = 3; @interface StatusItemView : NSView { atom::TrayIconCocoa* trayIcon_; // weak - AtomMenuController* menu_controller_; // weak + AtomMenuController* menuController_; // weak BOOL isHighlightEnable_; BOOL inMouseEventSequence_; base::scoped_nsobject image_; @@ -33,7 +32,10 @@ const CGFloat kMargin = 3; isHighlightEnable_ = YES; statusItem_.reset([[[NSStatusBar systemStatusBar] statusItemWithLength: NSVariableStatusItemLength] retain]); - NSRect frame = NSMakeRect(0, 0, 26, [[statusItem_ statusBar] thickness]); + NSRect frame = NSMakeRect(0, + 0, + kStatusItemLength, + [[statusItem_ statusBar] thickness]); if ((self = [super initWithFrame:frame])) { [statusItem_ setView:self]; } @@ -46,38 +48,52 @@ const CGFloat kMargin = 3; } - (void)drawRect:(NSRect)dirtyRect { + // Draw the tray icon and title that align with NSSStatusItem, layout: + // ---------------- + // | icon | title | + /// ---------------- BOOL highlight = [self shouldHighlight]; - [statusItem_ drawStatusBarBackgroundInRect:[self bounds] + CGFloat titleWidth = [self titleWidth]; + // Calculate the total icon bounds. + NSRect statusItemBounds = NSMakeRect(0, + 0, + kStatusItemLength + titleWidth, + [[statusItem_ statusBar] thickness]); + [statusItem_ drawStatusBarBackgroundInRect:statusItemBounds withHighlight:highlight]; - NSRect icon_frame = NSMakeRect(0, - 0, - kStatusItemLength, - [[statusItem_ statusBar] thickness]); - NSRect icon_draw_rect = NSInsetRect(icon_frame, kMargin, kMargin); + [statusItem_ setLength:titleWidth + kStatusItemLength]; + if (title_) { + NSRect titleDrawRect = NSMakeRect(kStatusItemLength, + 0, + titleWidth + kStatusItemLength, + [[statusItem_ statusBar] thickness]); + [title_ drawInRect:titleDrawRect + withAttributes:[self titleAttributes]]; + } + + NSRect iconRect = NSMakeRect(0, + 0, + kStatusItemLength, + [[statusItem_ statusBar] thickness]); if (highlight && alternateImage_) { - [alternateImage_ drawInRect:icon_draw_rect + [alternateImage_ drawInRect:NSInsetRect(iconRect, kMargin, kMargin) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1]; } else { - [image_ drawInRect:icon_draw_rect + [image_ drawInRect:NSInsetRect(iconRect, kMargin, kMargin) fromRect:NSZeroRect operation:NSCompositeSourceOver fraction:1]; } - if (title_) { - NSAttributedString* attributes = - [[NSAttributedString alloc] initWithString:title_ - attributes:[self titleAttributes]]; - CGFloat title_width = [attributes size].width; - NSRect title_rect = NSMakeRect(kStatusItemLength, - 0, - title_width + kStatusItemLength, - [[statusItem_ statusBar] thickness]); - [title_ drawInRect:title_rect - withAttributes:[self titleAttributes]]; - [statusItem_ setLength:title_width + kStatusItemLength]; - } +} + +- (CGFloat) titleWidth { + if (!title_) return 0; + NSAttributedString* attributes = + [[NSAttributedString alloc] initWithString:title_ + attributes:[self titleAttributes]]; + return [attributes size].width; } - (NSDictionary *)titleAttributes { @@ -103,12 +119,11 @@ const CGFloat kMargin = 3; } -(void)setTitle:(NSString*) title { - //title_= [title retain]; title_.reset([title copy]); } - (void)setMenuController:(AtomMenuController*)menu { - menu_controller_ = menu; + menuController_ = menu; } -(void)mouseDown:(NSEvent *)event { @@ -125,14 +140,14 @@ const CGFloat kMargin = 3; } inMouseEventSequence_ = NO; if (event.clickCount == 1) { - if (menu_controller_) { - [statusItem_ popUpStatusItemMenu:[menu_controller_ menu]]; + if (menuController_) { + [statusItem_ popUpStatusItemMenu:[menuController_ menu]]; } trayIcon_->NotifyClicked([self getBoundsFromEvent:event]); } - if (event.clickCount == 2 && !menu_controller_) { + if (event.clickCount == 2 && !menuController_) { trayIcon_->NotifyDoubleClicked(); } [self setNeedsDisplay:YES]; @@ -143,7 +158,7 @@ const CGFloat kMargin = 3; } -(BOOL) shouldHighlight { - BOOL is_menu_open = [menu_controller_ isMenuOpen]; + BOOL is_menu_open = [menuController_ isMenuOpen]; return isHighlightEnable_ && (inMouseEventSequence_ || is_menu_open); } From 4421fbf9f36986d9e8c0302b1ccbfdcebe67cd62 Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Thu, 16 Jul 2015 10:50:53 +0800 Subject: [PATCH 06/11] Implement 'tray.popContextMenu' API on OS X. --- atom/browser/api/atom_api_tray.cc | 5 +++++ atom/browser/api/atom_api_tray.h | 1 + atom/browser/ui/tray_icon.cc | 3 +++ atom/browser/ui/tray_icon.h | 2 ++ atom/browser/ui/tray_icon_cocoa.h | 1 + atom/browser/ui/tray_icon_cocoa.mm | 15 +++++++++++++++ 6 files changed, 27 insertions(+) diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc index 7c1bfdfac95a..6fd9599d32bc 100644 --- a/atom/browser/api/atom_api_tray.cc +++ b/atom/browser/api/atom_api_tray.cc @@ -106,6 +106,10 @@ void Tray::DisplayBalloon(mate::Arguments* args, tray_icon_->DisplayBalloon(icon, title, content); } +void Tray::PopContextMenu() { + tray_icon_->PopContextMenu(); +} + void Tray::SetContextMenu(mate::Arguments* args, Menu* menu) { tray_icon_->SetContextMenu(menu->model()); } @@ -121,6 +125,7 @@ void Tray::BuildPrototype(v8::Isolate* isolate, .SetMethod("setTitle", &Tray::SetTitle) .SetMethod("setHighlightMode", &Tray::SetHighlightMode) .SetMethod("displayBalloon", &Tray::DisplayBalloon) + .SetMethod("popContextMenu", &Tray::PopContextMenu) .SetMethod("_setContextMenu", &Tray::SetContextMenu); } diff --git a/atom/browser/api/atom_api_tray.h b/atom/browser/api/atom_api_tray.h index 366a847ac602..e7df28548c17 100644 --- a/atom/browser/api/atom_api_tray.h +++ b/atom/browser/api/atom_api_tray.h @@ -58,6 +58,7 @@ class Tray : public mate::EventEmitter, void SetTitle(mate::Arguments* args, const std::string& title); void SetHighlightMode(mate::Arguments* args, bool highlight); void DisplayBalloon(mate::Arguments* args, const mate::Dictionary& options); + void PopContextMenu(); void SetContextMenu(mate::Arguments* args, Menu* menu); private: diff --git a/atom/browser/ui/tray_icon.cc b/atom/browser/ui/tray_icon.cc index 4f801faf862e..7f67a02160a6 100644 --- a/atom/browser/ui/tray_icon.cc +++ b/atom/browser/ui/tray_icon.cc @@ -26,6 +26,9 @@ void TrayIcon::DisplayBalloon(const gfx::Image& icon, const base::string16& contents) { } +void TrayIcon::PopContextMenu() { +} + void TrayIcon::NotifyClicked(const gfx::Rect& bounds) { FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnClicked(bounds)); } diff --git a/atom/browser/ui/tray_icon.h b/atom/browser/ui/tray_icon.h index 4e097e0ac84a..e57d751b0713 100644 --- a/atom/browser/ui/tray_icon.h +++ b/atom/browser/ui/tray_icon.h @@ -46,6 +46,8 @@ class TrayIcon { const base::string16& title, const base::string16& contents); + virtual void PopContextMenu(); + // Set the context menu for this icon. virtual void SetContextMenu(ui::SimpleMenuModel* menu_model) = 0; diff --git a/atom/browser/ui/tray_icon_cocoa.h b/atom/browser/ui/tray_icon_cocoa.h index d4cfd431e466..eb80250f6fc0 100644 --- a/atom/browser/ui/tray_icon_cocoa.h +++ b/atom/browser/ui/tray_icon_cocoa.h @@ -27,6 +27,7 @@ class TrayIconCocoa : public TrayIcon { void SetToolTip(const std::string& tool_tip) override; void SetTitle(const std::string& title) override; void SetHighlightMode(bool highlight) override; + void PopContextMenu() override; void SetContextMenu(ui::SimpleMenuModel* menu_model) override; private: diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index 514631c21065..f751f131b4e2 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -153,6 +153,17 @@ const CGFloat kMargin = 3; [self setNeedsDisplay:YES]; } +- (void)popContextMenu { + if (menuController_ && ![menuController_ isMenuOpen]) { + // redraw the dray icon to show highlight if it is enabled. + [self setNeedsDisplay:YES]; + [statusItem_ popUpStatusItemMenu:[menuController_ menu]]; + // The popUpStatusItemMenu returns only after the showing menu is closed. + // When it returns, we need to redraw the tray icon to not show highlight. + [self setNeedsDisplay:YES]; + } +} + - (void)rightMouseUp:(NSEvent*)event { trayIcon_->NotifyRightClicked([self getBoundsFromEvent:event]); } @@ -201,6 +212,10 @@ void TrayIconCocoa::SetHighlightMode(bool highlight) { [status_item_view_ setHighlight:highlight]; } +void TrayIconCocoa::PopContextMenu() { + [status_item_view_ popContextMenu]; +} + void TrayIconCocoa::SetContextMenu(ui::SimpleMenuModel* menu_model) { menu_.reset([[AtomMenuController alloc] initWithModel:menu_model]); [status_item_view_ setMenuController:menu_.get()]; From ed4c69343f5f9bf77cec5ec84f3a363261b82b21 Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Thu, 16 Jul 2015 11:39:49 +0800 Subject: [PATCH 07/11] Add 'tray.popContextMenu()' Windows implementation. --- atom/browser/api/atom_api_tray.cc | 6 +++-- atom/browser/api/atom_api_tray.h | 2 +- atom/browser/ui/tray_icon.cc | 2 +- atom/browser/ui/tray_icon.h | 2 +- atom/browser/ui/win/notify_icon.cc | 36 +++++++++++++++--------------- atom/browser/ui/win/notify_icon.h | 1 + 6 files changed, 26 insertions(+), 23 deletions(-) diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc index 6fd9599d32bc..bf3a521d76e7 100644 --- a/atom/browser/api/atom_api_tray.cc +++ b/atom/browser/api/atom_api_tray.cc @@ -106,8 +106,10 @@ void Tray::DisplayBalloon(mate::Arguments* args, tray_icon_->DisplayBalloon(icon, title, content); } -void Tray::PopContextMenu() { - tray_icon_->PopContextMenu(); +void Tray::PopContextMenu(mate::Arguments* args) { + gfx::Point pos; + args->GetNext(&pos); + tray_icon_->PopContextMenu(pos); } void Tray::SetContextMenu(mate::Arguments* args, Menu* menu) { diff --git a/atom/browser/api/atom_api_tray.h b/atom/browser/api/atom_api_tray.h index e7df28548c17..5b40937d717a 100644 --- a/atom/browser/api/atom_api_tray.h +++ b/atom/browser/api/atom_api_tray.h @@ -58,7 +58,7 @@ class Tray : public mate::EventEmitter, void SetTitle(mate::Arguments* args, const std::string& title); void SetHighlightMode(mate::Arguments* args, bool highlight); void DisplayBalloon(mate::Arguments* args, const mate::Dictionary& options); - void PopContextMenu(); + void PopContextMenu(mate::Arguments* args); void SetContextMenu(mate::Arguments* args, Menu* menu); private: diff --git a/atom/browser/ui/tray_icon.cc b/atom/browser/ui/tray_icon.cc index 7f67a02160a6..ab1ebfa67790 100644 --- a/atom/browser/ui/tray_icon.cc +++ b/atom/browser/ui/tray_icon.cc @@ -26,7 +26,7 @@ void TrayIcon::DisplayBalloon(const gfx::Image& icon, const base::string16& contents) { } -void TrayIcon::PopContextMenu() { +void TrayIcon::PopContextMenu(const gfx::Point& pos) { } void TrayIcon::NotifyClicked(const gfx::Rect& bounds) { diff --git a/atom/browser/ui/tray_icon.h b/atom/browser/ui/tray_icon.h index e57d751b0713..a9e9b414ec94 100644 --- a/atom/browser/ui/tray_icon.h +++ b/atom/browser/ui/tray_icon.h @@ -46,7 +46,7 @@ class TrayIcon { const base::string16& title, const base::string16& contents); - virtual void PopContextMenu(); + virtual void PopContextMenu(const gfx::Point& pos); // Set the context menu for this icon. virtual void SetContextMenu(ui::SimpleMenuModel* menu_model) = 0; diff --git a/atom/browser/ui/win/notify_icon.cc b/atom/browser/ui/win/notify_icon.cc index 64329a63e5e4..bc9ff46f939a 100644 --- a/atom/browser/ui/win/notify_icon.cc +++ b/atom/browser/ui/win/notify_icon.cc @@ -62,24 +62,7 @@ void NotifyIcon::HandleClickEvent(const gfx::Point& cursor_pos, } NotifyRightClicked(gfx::Rect(rect)); - - if (!menu_model_) - return; - - // Set our window as the foreground window, so the context menu closes when - // we click away from it. - if (!SetForegroundWindow(window_)) - return; - - views::MenuRunner menu_runner( - menu_model_, - views::MenuRunner::CONTEXT_MENU | views::MenuRunner::HAS_MNEMONICS); - ignore_result(menu_runner.RunMenuAt( - NULL, - NULL, - gfx::Rect(cursor_pos, gfx::Size()), - views::MENU_ANCHOR_TOPLEFT, - ui::MENU_SOURCE_MOUSE)); + PopContextMenu(cursor_pos); } void NotifyIcon::ResetIcon() { @@ -152,6 +135,23 @@ void NotifyIcon::DisplayBalloon(const gfx::Image& icon, LOG(WARNING) << "Unable to create status tray balloon."; } +void NotifyIcon::PopContextMenu(const gfx::Point& pos) { + // Set our window as the foreground window, so the context menu closes when + // we click away from it. + if (!SetForegroundWindow(window_)) + return; + + views::MenuRunner menu_runner( + menu_model_, + views::MenuRunner::CONTEXT_MENU | views::MenuRunner::HAS_MNEMONICS); + ignore_result(menu_runner.RunMenuAt( + NULL, + NULL, + gfx::Rect(pos, gfx::Size()), + views::MENU_ANCHOR_TOPLEFT, + ui::MENU_SOURCE_MOUSE)); +} + void NotifyIcon::SetContextMenu(ui::SimpleMenuModel* menu_model) { menu_model_ = menu_model; } diff --git a/atom/browser/ui/win/notify_icon.h b/atom/browser/ui/win/notify_icon.h index 12eea1fcf725..8e00f1267920 100644 --- a/atom/browser/ui/win/notify_icon.h +++ b/atom/browser/ui/win/notify_icon.h @@ -49,6 +49,7 @@ class NotifyIcon : public TrayIcon { void DisplayBalloon(const gfx::Image& icon, const base::string16& title, const base::string16& contents) override; + void PopContextMenu(const gfx::Point& pos) override; void SetContextMenu(ui::SimpleMenuModel* menu_model) override; private: From 736fe0c1db8c2fb5502eb1a6bb5d3cca9c63f750 Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Thu, 16 Jul 2015 11:43:42 +0800 Subject: [PATCH 08/11] Fix OS X build error. --- atom/browser/ui/tray_icon_cocoa.h | 2 +- atom/browser/ui/tray_icon_cocoa.mm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/atom/browser/ui/tray_icon_cocoa.h b/atom/browser/ui/tray_icon_cocoa.h index eb80250f6fc0..9aa801ec5ead 100644 --- a/atom/browser/ui/tray_icon_cocoa.h +++ b/atom/browser/ui/tray_icon_cocoa.h @@ -27,7 +27,7 @@ class TrayIconCocoa : public TrayIcon { void SetToolTip(const std::string& tool_tip) override; void SetTitle(const std::string& title) override; void SetHighlightMode(bool highlight) override; - void PopContextMenu() override; + void PopContextMenu(const gfx::Point& pos) override; void SetContextMenu(ui::SimpleMenuModel* menu_model) override; private: diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index f751f131b4e2..8d1748e3f6d1 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -212,7 +212,7 @@ void TrayIconCocoa::SetHighlightMode(bool highlight) { [status_item_view_ setHighlight:highlight]; } -void TrayIconCocoa::PopContextMenu() { +void TrayIconCocoa::PopContextMenu(const gfx::Point& pos) { [status_item_view_ popContextMenu]; } From 17628b3e40a8793cc4e65f5e4bc62948520ee1cd Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Thu, 16 Jul 2015 11:53:13 +0800 Subject: [PATCH 09/11] :memo: tray.popContextMenu API. --- docs/api/tray.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/api/tray.md b/docs/api/tray.md index 6d1bbb75ec6c..fe0e2542b3a3 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -144,6 +144,15 @@ Displays a tray balloon. __Note:__ This is only implemented on Windows. +### Tray.popContextMenu([position]) + +* `position` Object - The pop position + * `x` Integer + * `y` Integer + +__Note:__ This is only implemented on OS X and Windows. +The `position` is only available on Windows, and it is (0, 0) by default. + ### Tray.setContextMenu(menu) * `menu` Menu From 2cd6ad1a9712cdf9cf97564de1d3c382ce2f7c3f Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Thu, 16 Jul 2015 18:36:45 +0800 Subject: [PATCH 10/11] More code style fixing. --- atom/browser/ui/tray_icon_cocoa.mm | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index 8d1748e3f6d1..4dcb6d2782c1 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -9,9 +9,13 @@ #include "ui/gfx/image/image.h" #include "ui/gfx/screen.h" +namespace { + const CGFloat kStatusItemLength = 26; const CGFloat kMargin = 3; +} // namespace + @interface StatusItemView : NSView { atom::TrayIconCocoa* trayIcon_; // weak AtomMenuController* menuController_; // weak @@ -88,7 +92,7 @@ const CGFloat kMargin = 3; } } -- (CGFloat) titleWidth { +- (CGFloat)titleWidth { if (!title_) return 0; NSAttributedString* attributes = [[NSAttributedString alloc] initWithString:title_ @@ -96,7 +100,7 @@ const CGFloat kMargin = 3; return [attributes size].width; } -- (NSDictionary *)titleAttributes { +- (NSDictionary*)titleAttributes { NSFont* font = [NSFont menuBarFontOfSize:0]; NSColor* foregroundColor = [NSColor blackColor]; @@ -108,6 +112,7 @@ const CGFloat kMargin = 3; - (void)setImage:(NSImage*)image { image_.reset([image copy]); + [self setNeedsDisplay:YES]; } - (void)setAlternateImage:(NSImage*)image { @@ -118,20 +123,21 @@ const CGFloat kMargin = 3; isHighlightEnable_ = highlight; } --(void)setTitle:(NSString*) title { +- (void)setTitle:(NSString*)title { title_.reset([title copy]); + [self setNeedsDisplay:YES]; } - (void)setMenuController:(AtomMenuController*)menu { menuController_ = menu; } --(void)mouseDown:(NSEvent *)event { +- (void)mouseDown:(NSEvent*)event { inMouseEventSequence_ = YES; [self setNeedsDisplay:YES]; } --(void)mouseUp:(NSEvent *)event { +- (void)mouseUp:(NSEvent*)event { if (!inMouseEventSequence_) { // If the menu is showing, when user clicked the tray icon, the `mouseDown` // event will be dissmissed, we need to close the menu at this time. @@ -168,12 +174,12 @@ const CGFloat kMargin = 3; trayIcon_->NotifyRightClicked([self getBoundsFromEvent:event]); } --(BOOL) shouldHighlight { +- (BOOL)shouldHighlight { BOOL is_menu_open = [menuController_ isMenuOpen]; return isHighlightEnable_ && (inMouseEventSequence_ || is_menu_open); } --(gfx::Rect) getBoundsFromEvent:(NSEvent*)event { +- (gfx::Rect)getBoundsFromEvent:(NSEvent*)event { NSRect frame = event.window.frame; gfx::Rect bounds(frame.origin.x, 0, NSWidth(frame), NSHeight(frame)); NSScreen* screen = [[NSScreen screens] objectAtIndex:0]; From d342c9a6dff2077d50bb34ad33ac6cb70441a81c Mon Sep 17 00:00:00 2001 From: Haojian Wu Date: Sun, 19 Jul 2015 12:12:28 +0800 Subject: [PATCH 11/11] Implement 'drop-files' tray event on OS X. --- atom/browser/api/atom_api_tray.cc | 4 ++++ atom/browser/api/atom_api_tray.h | 6 ++++-- atom/browser/ui/tray_icon.cc | 4 ++++ atom/browser/ui/tray_icon.h | 2 ++ atom/browser/ui/tray_icon_cocoa.mm | 20 ++++++++++++++++++++ atom/browser/ui/tray_icon_observer.h | 8 ++++++-- docs/api/tray.md | 9 +++++++++ 7 files changed, 49 insertions(+), 4 deletions(-) diff --git a/atom/browser/api/atom_api_tray.cc b/atom/browser/api/atom_api_tray.cc index bf3a521d76e7..a209537c940f 100644 --- a/atom/browser/api/atom_api_tray.cc +++ b/atom/browser/api/atom_api_tray.cc @@ -64,6 +64,10 @@ void Tray::OnRightClicked(const gfx::Rect& bounds) { Emit("right-clicked", bounds); } +void Tray::OnDropFiles(const std::vector& files) { + Emit("drop-files", files); +} + bool Tray::IsDestroyed() const { return !tray_icon_; } diff --git a/atom/browser/api/atom_api_tray.h b/atom/browser/api/atom_api_tray.h index 5b40937d717a..10ea3836819e 100644 --- a/atom/browser/api/atom_api_tray.h +++ b/atom/browser/api/atom_api_tray.h @@ -6,6 +6,7 @@ #define ATOM_BROWSER_API_ATOM_API_TRAY_H_ #include +#include #include "atom/browser/api/event_emitter.h" #include "atom/browser/ui/tray_icon_observer.h" @@ -41,12 +42,13 @@ class Tray : public mate::EventEmitter, virtual ~Tray(); // TrayIconObserver: - void OnClicked(const gfx::Rect&) override; + void OnClicked(const gfx::Rect& bounds) override; void OnDoubleClicked() override; void OnBalloonShow() override; void OnBalloonClicked() override; void OnBalloonClosed() override; - void OnRightClicked(const gfx::Rect&) override; + void OnRightClicked(const gfx::Rect& bounds) override; + void OnDropFiles(const std::vector& files) override; // mate::Wrappable: bool IsDestroyed() const override; diff --git a/atom/browser/ui/tray_icon.cc b/atom/browser/ui/tray_icon.cc index ab1ebfa67790..456cbe47a5a3 100644 --- a/atom/browser/ui/tray_icon.cc +++ b/atom/browser/ui/tray_icon.cc @@ -53,4 +53,8 @@ void TrayIcon::NotifyRightClicked(const gfx::Rect& bounds) { FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnRightClicked(bounds)); } +void TrayIcon::NotfiyDropFiles(const std::vector& files) { + FOR_EACH_OBSERVER(TrayIconObserver, observers_, OnDropFiles(files)); +} + } // namespace atom diff --git a/atom/browser/ui/tray_icon.h b/atom/browser/ui/tray_icon.h index a9e9b414ec94..d6885699d01d 100644 --- a/atom/browser/ui/tray_icon.h +++ b/atom/browser/ui/tray_icon.h @@ -6,6 +6,7 @@ #define ATOM_BROWSER_UI_TRAY_ICON_H_ #include +#include #include "atom/browser/ui/tray_icon_observer.h" #include "base/observer_list.h" @@ -59,6 +60,7 @@ class TrayIcon { void NotifyBalloonClicked(); void NotifyBalloonClosed(); void NotifyRightClicked(const gfx::Rect& bounds = gfx::Rect()); + void NotfiyDropFiles(const std::vector& files); protected: TrayIcon(); diff --git a/atom/browser/ui/tray_icon_cocoa.mm b/atom/browser/ui/tray_icon_cocoa.mm index 4dcb6d2782c1..fddbd8c16eb0 100644 --- a/atom/browser/ui/tray_icon_cocoa.mm +++ b/atom/browser/ui/tray_icon_cocoa.mm @@ -41,6 +41,8 @@ const CGFloat kMargin = 3; kStatusItemLength, [[statusItem_ statusBar] thickness]); if ((self = [super initWithFrame:frame])) { + [self registerForDraggedTypes: + [NSArray arrayWithObjects:NSFilenamesPboardType, nil]]; [statusItem_ setView:self]; } return self; @@ -174,6 +176,24 @@ const CGFloat kMargin = 3; trayIcon_->NotifyRightClicked([self getBoundsFromEvent:event]); } +- (NSDragOperation)draggingEntered:(id )sender { + return NSDragOperationCopy; +} + +- (BOOL)performDragOperation:(id )sender { + NSPasteboard* pboard = [sender draggingPasteboard]; + + if ([[pboard types] containsObject:NSFilenamesPboardType]) { + std::vector dropFiles; + NSArray* files = [pboard propertyListForType:NSFilenamesPboardType]; + for (NSString* file in files) + dropFiles.push_back(base::SysNSStringToUTF8(file)); + trayIcon_->NotfiyDropFiles(dropFiles); + return YES; + } + return NO; +} + - (BOOL)shouldHighlight { BOOL is_menu_open = [menuController_ isMenuOpen]; return isHighlightEnable_ && (inMouseEventSequence_ || is_menu_open); diff --git a/atom/browser/ui/tray_icon_observer.h b/atom/browser/ui/tray_icon_observer.h index 061115efdc64..6c9839a38a1f 100644 --- a/atom/browser/ui/tray_icon_observer.h +++ b/atom/browser/ui/tray_icon_observer.h @@ -5,6 +5,9 @@ #ifndef ATOM_BROWSER_UI_TRAY_ICON_OBSERVER_H_ #define ATOM_BROWSER_UI_TRAY_ICON_OBSERVER_H_ +#include +#include + namespace gfx { class Rect; } @@ -13,12 +16,13 @@ namespace atom { class TrayIconObserver { public: - virtual void OnClicked(const gfx::Rect&) {} + virtual void OnClicked(const gfx::Rect& bounds) {} virtual void OnDoubleClicked() {} virtual void OnBalloonShow() {} virtual void OnBalloonClicked() {} virtual void OnBalloonClosed() {} - virtual void OnRightClicked(const gfx::Rect&) {} + virtual void OnRightClicked(const gfx::Rect& bounds) {} + virtual void OnDropFiles(const std::vector& files) {} protected: virtual ~TrayIconObserver() {} diff --git a/docs/api/tray.md b/docs/api/tray.md index fe0e2542b3a3..30225dd6f3ed 100644 --- a/docs/api/tray.md +++ b/docs/api/tray.md @@ -95,6 +95,15 @@ closes it. __Note:__ This is only implemented on Windows. +### Event: 'drop-files' + +* `event` +* `files` Array - the file path of dropped files. + +Emitted when dragged files are dropped in the tray icon. + +__Note:__ This is only implemented on OS X. + ### Tray.destroy() Destroys the tray icon immediately.