| // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "chrome/browser/cocoa/reload_button.h" |
| |
| #include "app/l10n_util.h" |
| #include "app/l10n_util_mac.h" |
| #include "base/nsimage_cache_mac.h" |
| #include "chrome/app/chrome_command_ids.h" |
| #import "chrome/browser/cocoa/gradient_button_cell.h" |
| #import "chrome/browser/cocoa/view_id_util.h" |
| #include "grit/generated_resources.h" |
| |
| namespace { |
| |
| NSString* const kReloadImageName = @"reload_Template.pdf"; |
| NSString* const kStopImageName = @"stop_Template.pdf"; |
| |
| // Constant matches Windows. |
| NSTimeInterval kPendingReloadTimeout = 1.35; |
| |
| } // namespace |
| |
| @implementation ReloadButton |
| |
| - (void)dealloc { |
| if (trackingArea_) { |
| [self removeTrackingArea:trackingArea_]; |
| trackingArea_.reset(); |
| } |
| [super dealloc]; |
| } |
| |
| - (void)updateTrackingAreas { |
| // If the mouse is hovering when the tracking area is updated, the |
| // control could end up locked into inappropriate behavior for |
| // awhile, so unwind state. |
| if (isMouseInside_) |
| [self mouseExited:nil]; |
| |
| if (trackingArea_) { |
| [self removeTrackingArea:trackingArea_]; |
| trackingArea_.reset(); |
| } |
| trackingArea_.reset([[NSTrackingArea alloc] |
| initWithRect:[self bounds] |
| options:(NSTrackingMouseEnteredAndExited | |
| NSTrackingActiveInActiveApp) |
| owner:self |
| userInfo:nil]); |
| [self addTrackingArea:trackingArea_]; |
| } |
| |
| - (void)awakeFromNib { |
| [self updateTrackingAreas]; |
| |
| // Don't allow multi-clicks, because the user probably wouldn't ever |
| // want to stop+reload or reload+stop. |
| [self setIgnoresMultiClick:YES]; |
| } |
| |
| - (void)updateTag:(NSInteger)anInt { |
| if ([self tag] == anInt) |
| return; |
| |
| // Forcibly remove any stale tooltip which is being displayed. |
| [self removeAllToolTips]; |
| |
| [self setTag:anInt]; |
| if (anInt == IDC_RELOAD) { |
| [self setImage:nsimage_cache::ImageNamed(kReloadImageName)]; |
| [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_RELOAD)]; |
| } else if (anInt == IDC_STOP) { |
| [self setImage:nsimage_cache::ImageNamed(kStopImageName)]; |
| [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_STOP)]; |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| - (void)setIsLoading:(BOOL)isLoading force:(BOOL)force { |
| // Can always transition to stop mode. Only transition to reload |
| // mode if forced or if the mouse isn't hovering. Otherwise, note |
| // that reload mode is desired and disable the button. |
| if (isLoading) { |
| pendingReloadTimer_.reset(); |
| [self updateTag:IDC_STOP]; |
| [self setEnabled:YES]; |
| } else if (force || ![self isMouseInside]) { |
| pendingReloadTimer_.reset(); |
| [self updateTag:IDC_RELOAD]; |
| |
| // This button's cell may not have received a mouseExited event, and |
| // therefore it could still think that the mouse is inside the button. Make |
| // sure the cell's sense of mouse-inside matches the local sense, to prevent |
| // drawing artifacts. |
| id cell = [self cell]; |
| if ([cell respondsToSelector:@selector(setMouseInside:animate:)]) |
| [cell setMouseInside:[self isMouseInside] animate:NO]; |
| [self setEnabled:YES]; |
| } else if ([self tag] == IDC_STOP && !pendingReloadTimer_) { |
| [self setEnabled:NO]; |
| pendingReloadTimer_.reset( |
| [[NSTimer scheduledTimerWithTimeInterval:kPendingReloadTimeout |
| target:self |
| selector:@selector(forceReloadState) |
| userInfo:nil |
| repeats:NO] retain]); |
| } |
| } |
| |
| - (void)forceReloadState { |
| [self setIsLoading:NO force:YES]; |
| } |
| |
| - (BOOL)sendAction:(SEL)theAction to:(id)theTarget { |
| if ([self tag] == IDC_STOP) { |
| // When the timer is started, the button is disabled, so this |
| // should not be possible. |
| DCHECK(!pendingReloadTimer_.get()); |
| |
| // When the stop is processed, immediately change to reload mode, |
| // even though the IPC still has to bounce off the renderer and |
| // back before the regular |-setIsLoaded:force:| will be called. |
| // [This is how views and gtk do it.] |
| const BOOL ret = [super sendAction:theAction to:theTarget]; |
| if (ret) |
| [self forceReloadState]; |
| return ret; |
| } |
| |
| return [super sendAction:theAction to:theTarget]; |
| } |
| |
| - (void)mouseEntered:(NSEvent*)theEvent { |
| isMouseInside_ = YES; |
| } |
| |
| - (void)mouseExited:(NSEvent*)theEvent { |
| isMouseInside_ = NO; |
| |
| // Reload mode was requested during the hover. |
| if (pendingReloadTimer_) |
| [self forceReloadState]; |
| } |
| |
| - (BOOL)isMouseInside { |
| return isMouseInside_; |
| } |
| |
| - (ViewID)viewID { |
| return VIEW_ID_RELOAD_BUTTON; |
| } |
| |
| @end // ReloadButton |
| |
| @implementation ReloadButton (Testing) |
| |
| + (void)setPendingReloadTimeout:(NSTimeInterval)seconds { |
| kPendingReloadTimeout = seconds; |
| } |
| |
| - (NSTrackingArea*)trackingArea { |
| return trackingArea_; |
| } |
| |
| @end |