1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #import "chrome/browser/ui/cocoa/toolbar/reload_button.h" 6 7 #include "ui/base/l10n/l10n_util.h" 8 #include "app/mac/nsimage_cache.h" 9 #include "chrome/app/chrome_command_ids.h" 10 #import "chrome/browser/ui/cocoa/gradient_button_cell.h" 11 #import "chrome/browser/ui/cocoa/view_id_util.h" 12 #include "grit/generated_resources.h" 13 #include "ui/base/l10n/l10n_util_mac.h" 14 15 namespace { 16 17 NSString* const kReloadImageName = @"reload_Template.pdf"; 18 NSString* const kStopImageName = @"stop_Template.pdf"; 19 20 // Constant matches Windows. 21 NSTimeInterval kPendingReloadTimeout = 1.35; 22 23 } // namespace 24 25 @implementation ReloadButton 26 27 - (void)dealloc { 28 if (trackingArea_) { 29 [self removeTrackingArea:trackingArea_]; 30 trackingArea_.reset(); 31 } 32 [super dealloc]; 33 } 34 35 - (void)updateTrackingAreas { 36 // If the mouse is hovering when the tracking area is updated, the 37 // control could end up locked into inappropriate behavior for 38 // awhile, so unwind state. 39 if (isMouseInside_) 40 [self mouseExited:nil]; 41 42 if (trackingArea_) { 43 [self removeTrackingArea:trackingArea_]; 44 trackingArea_.reset(); 45 } 46 trackingArea_.reset([[NSTrackingArea alloc] 47 initWithRect:[self bounds] 48 options:(NSTrackingMouseEnteredAndExited | 49 NSTrackingActiveInActiveApp) 50 owner:self 51 userInfo:nil]); 52 [self addTrackingArea:trackingArea_]; 53 } 54 55 - (void)awakeFromNib { 56 [self updateTrackingAreas]; 57 58 // Don't allow multi-clicks, because the user probably wouldn't ever 59 // want to stop+reload or reload+stop. 60 [self setIgnoresMultiClick:YES]; 61 } 62 63 - (void)updateTag:(NSInteger)anInt { 64 if ([self tag] == anInt) 65 return; 66 67 // Forcibly remove any stale tooltip which is being displayed. 68 [self removeAllToolTips]; 69 70 [self setTag:anInt]; 71 if (anInt == IDC_RELOAD) { 72 [self setImage:app::mac::GetCachedImageWithName(kReloadImageName)]; 73 [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_RELOAD)]; 74 } else if (anInt == IDC_STOP) { 75 [self setImage:app::mac::GetCachedImageWithName(kStopImageName)]; 76 [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_STOP)]; 77 } else { 78 NOTREACHED(); 79 } 80 } 81 82 - (void)setIsLoading:(BOOL)isLoading force:(BOOL)force { 83 // Can always transition to stop mode. Only transition to reload 84 // mode if forced or if the mouse isn't hovering. Otherwise, note 85 // that reload mode is desired and disable the button. 86 if (isLoading) { 87 pendingReloadTimer_.reset(); 88 [self updateTag:IDC_STOP]; 89 [self setEnabled:YES]; 90 } else if (force || ![self isMouseInside]) { 91 pendingReloadTimer_.reset(); 92 [self updateTag:IDC_RELOAD]; 93 94 // This button's cell may not have received a mouseExited event, and 95 // therefore it could still think that the mouse is inside the button. Make 96 // sure the cell's sense of mouse-inside matches the local sense, to prevent 97 // drawing artifacts. 98 id cell = [self cell]; 99 if ([cell respondsToSelector:@selector(setMouseInside:animate:)]) 100 [cell setMouseInside:[self isMouseInside] animate:NO]; 101 [self setEnabled:YES]; 102 } else if ([self tag] == IDC_STOP && !pendingReloadTimer_) { 103 [self setEnabled:NO]; 104 pendingReloadTimer_.reset( 105 [[NSTimer scheduledTimerWithTimeInterval:kPendingReloadTimeout 106 target:self 107 selector:@selector(forceReloadState) 108 userInfo:nil 109 repeats:NO] retain]); 110 } 111 } 112 113 - (void)forceReloadState { 114 [self setIsLoading:NO force:YES]; 115 } 116 117 - (BOOL)sendAction:(SEL)theAction to:(id)theTarget { 118 if ([self tag] == IDC_STOP) { 119 // When the timer is started, the button is disabled, so this 120 // should not be possible. 121 DCHECK(!pendingReloadTimer_.get()); 122 123 // When the stop is processed, immediately change to reload mode, 124 // even though the IPC still has to bounce off the renderer and 125 // back before the regular |-setIsLoaded:force:| will be called. 126 // [This is how views and gtk do it.] 127 const BOOL ret = [super sendAction:theAction to:theTarget]; 128 if (ret) 129 [self forceReloadState]; 130 return ret; 131 } 132 133 return [super sendAction:theAction to:theTarget]; 134 } 135 136 - (void)mouseEntered:(NSEvent*)theEvent { 137 isMouseInside_ = YES; 138 } 139 140 - (void)mouseExited:(NSEvent*)theEvent { 141 isMouseInside_ = NO; 142 143 // Reload mode was requested during the hover. 144 if (pendingReloadTimer_) 145 [self forceReloadState]; 146 } 147 148 - (BOOL)isMouseInside { 149 return isMouseInside_; 150 } 151 152 - (ViewID)viewID { 153 return VIEW_ID_RELOAD_BUTTON; 154 } 155 156 @end // ReloadButton 157 158 @implementation ReloadButton (Testing) 159 160 + (void)setPendingReloadTimeout:(NSTimeInterval)seconds { 161 kPendingReloadTimeout = seconds; 162 } 163 164 - (NSTrackingArea*)trackingArea { 165 return trackingArea_; 166 } 167 168 @end 169