Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #import "chrome/browser/ui/cocoa/toolbar/reload_button.h" | 5 #import "chrome/browser/ui/cocoa/toolbar/reload_button.h" |
| 6 | 6 |
| 7 #include "chrome/app/chrome_command_ids.h" | 7 #include "chrome/app/chrome_command_ids.h" |
| 8 #import "chrome/browser/ui/cocoa/image_button_cell.h" | |
| 9 #import "chrome/browser/ui/cocoa/view_id_util.h" | 8 #import "chrome/browser/ui/cocoa/view_id_util.h" |
| 10 #include "grit/generated_resources.h" | 9 #include "grit/generated_resources.h" |
| 11 #include "grit/theme_resources.h" | 10 #include "grit/theme_resources.h" |
| 12 #include "grit/theme_resources_standard.h" | 11 #include "grit/theme_resources_standard.h" |
| 13 #include "ui/base/l10n/l10n_util.h" | 12 #include "ui/base/l10n/l10n_util.h" |
| 14 #include "ui/base/l10n/l10n_util_mac.h" | 13 #include "ui/base/l10n/l10n_util_mac.h" |
| 15 | 14 |
| 16 namespace { | 15 namespace { |
| 17 | 16 |
| 18 // Constant matches Windows. | 17 // Constant matches Windows. |
| 19 NSTimeInterval kPendingReloadTimeout = 1.35; | 18 NSTimeInterval kPendingReloadTimeout = 1.35; |
| 20 | 19 |
| 21 } // namespace | 20 } // namespace |
| 22 | 21 |
| 22 @interface ReloadButton () | |
| 23 - (void)invalidatePendingReloadTimer; | |
| 24 - (void)forceReloadState:(NSTimer *)timer; | |
| 25 @end | |
| 26 | |
| 23 @implementation ReloadButton | 27 @implementation ReloadButton |
| 24 | 28 |
| 25 + (Class)cellClass { | 29 + (Class)cellClass { |
| 26 return [ImageButtonCell class]; | 30 return [ImageButtonCell class]; |
| 27 } | 31 } |
| 28 | 32 |
| 29 - (void)dealloc { | 33 - (id)initWithFrame:(NSRect)frameRect { |
| 30 if (trackingArea_) { | 34 if ((self = [super initWithFrame:frameRect])) { |
| 31 [self removeTrackingArea:trackingArea_]; | 35 // Since this is not a custom view, -awakeFromNib won't be called twice. |
| 32 trackingArea_.reset(); | 36 [self awakeFromNib]; |
| 33 } | 37 } |
| 34 [super dealloc]; | 38 return self; |
| 35 } | 39 } |
| 36 | 40 |
| 37 - (void)updateTrackingAreas { | 41 - (void)viewWillMoveToWindow:(NSWindow *)newWindow { |
| 38 // If the mouse is hovering when the tracking area is updated, the | 42 // If this view is moved to a new window, reset its state. |
| 39 // control could end up locked into inappropriate behavior for | 43 [self setIsLoading:NO force:YES]; |
| 40 // awhile, so unwind state. | 44 [super viewWillMoveToWindow:newWindow]; |
| 41 if (isMouseInside_) | |
| 42 [self mouseExited:nil]; | |
| 43 | |
| 44 if (trackingArea_) { | |
| 45 [self removeTrackingArea:trackingArea_]; | |
| 46 trackingArea_.reset(); | |
| 47 } | |
| 48 trackingArea_.reset([[NSTrackingArea alloc] | |
| 49 initWithRect:[self bounds] | |
| 50 options:(NSTrackingMouseEnteredAndExited | | |
| 51 NSTrackingActiveInActiveApp) | |
| 52 owner:self | |
| 53 userInfo:nil]); | |
| 54 [self addTrackingArea:trackingArea_]; | |
| 55 } | 45 } |
| 56 | 46 |
| 57 - (void)awakeFromNib { | 47 - (void)awakeFromNib { |
| 58 [self updateTrackingAreas]; | |
| 59 | |
| 60 // Don't allow multi-clicks, because the user probably wouldn't ever | 48 // Don't allow multi-clicks, because the user probably wouldn't ever |
| 61 // want to stop+reload or reload+stop. | 49 // want to stop+reload or reload+stop. |
| 62 [self setIgnoresMultiClick:YES]; | 50 [self setIgnoresMultiClick:YES]; |
| 63 } | 51 } |
| 64 | 52 |
| 53 - (void)invalidatePendingReloadTimer { | |
| 54 [pendingReloadTimer_ invalidate]; | |
| 55 pendingReloadTimer_ = nil; | |
| 56 } | |
| 57 | |
| 65 - (void)updateTag:(NSInteger)anInt { | 58 - (void)updateTag:(NSInteger)anInt { |
| 66 if ([self tag] == anInt) | 59 if ([self tag] == anInt) |
| 67 return; | 60 return; |
| 68 | 61 |
| 69 // Forcibly remove any stale tooltip which is being displayed. | 62 // Forcibly remove any stale tooltip which is being displayed. |
| 70 [self removeAllToolTips]; | 63 [self removeAllToolTips]; |
| 71 | 64 id cell = [self cell]; |
| 72 [self setTag:anInt]; | 65 [self setTag:anInt]; |
| 73 if (anInt == IDC_RELOAD) { | 66 if (anInt == IDC_RELOAD) { |
| 74 [[self cell] setImageID:IDR_RELOAD | 67 [cell setImageID:IDR_RELOAD |
| 75 forButtonState:image_button_cell::kDefaultState]; | 68 forButtonState:image_button_cell::kDefaultState]; |
| 76 [[self cell] setImageID:IDR_RELOAD_H | 69 [cell setImageID:IDR_RELOAD_H |
| 77 forButtonState:image_button_cell::kHoverState]; | 70 forButtonState:image_button_cell::kHoverState]; |
| 78 [[self cell] setImageID:IDR_RELOAD_P | 71 [cell setImageID:IDR_RELOAD_P |
| 79 forButtonState:image_button_cell::kPressedState]; | 72 forButtonState:image_button_cell::kPressedState]; |
| 80 // The stop button has a disabled image but the reload button doesn't. To | 73 // The stop button has a disabled image but the reload button doesn't. To |
| 81 // unset it we have to explicilty change the image ID to 0. | 74 // unset it we have to explicilty change the image ID to 0. |
| 82 [[self cell] setImageID:0 | 75 [cell setImageID:0 |
| 83 forButtonState:image_button_cell::kDisabledState]; | 76 forButtonState:image_button_cell::kDisabledState]; |
| 84 [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_RELOAD)]; | 77 [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_RELOAD)]; |
| 85 } else if (anInt == IDC_STOP) { | 78 } else if (anInt == IDC_STOP) { |
| 86 [[self cell] setImageID:IDR_STOP | 79 [cell setImageID:IDR_STOP |
| 87 forButtonState:image_button_cell::kDefaultState]; | 80 forButtonState:image_button_cell::kDefaultState]; |
| 88 [[self cell] setImageID:IDR_STOP_H | 81 [cell setImageID:IDR_STOP_H |
| 89 forButtonState:image_button_cell::kHoverState]; | 82 forButtonState:image_button_cell::kHoverState]; |
| 90 [[self cell] setImageID:IDR_STOP_P | 83 [cell setImageID:IDR_STOP_P |
| 91 forButtonState:image_button_cell::kPressedState]; | 84 forButtonState:image_button_cell::kPressedState]; |
| 92 [[self cell] setImageID:IDR_STOP_D | 85 [cell setImageID:IDR_STOP_D |
| 93 forButtonState:image_button_cell::kDisabledState]; | 86 forButtonState:image_button_cell::kDisabledState]; |
| 94 [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_STOP)]; | 87 [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_STOP)]; |
| 95 } else { | 88 } else { |
| 96 NOTREACHED(); | 89 NOTREACHED(); |
| 97 } | 90 } |
| 98 } | 91 } |
| 99 | 92 |
| 93 - (id)accessibilityAttributeValue:(NSString *)attribute { | |
| 94 if ([attribute isEqualToString:NSAccessibilityEnabledAttribute] && | |
| 95 pendingReloadTimer_) { | |
| 96 return [NSNumber numberWithBool:NO]; | |
| 97 } else { | |
| 98 return [super accessibilityAttributeValue:attribute]; | |
| 99 } | |
| 100 } | |
| 101 | |
| 100 - (void)setIsLoading:(BOOL)isLoading force:(BOOL)force { | 102 - (void)setIsLoading:(BOOL)isLoading force:(BOOL)force { |
| 101 // Can always transition to stop mode. Only transition to reload | 103 // Can always transition to stop mode. Only transition to reload |
| 102 // mode if forced or if the mouse isn't hovering. Otherwise, note | 104 // mode if forced or if the mouse isn't hovering. Otherwise, note |
| 103 // that reload mode is desired and disable the button. | 105 // that reload mode is desired and disable the button. |
| 104 if (isLoading) { | 106 if (isLoading) { |
| 105 pendingReloadTimer_.reset(); | 107 [self invalidatePendingReloadTimer]; |
| 106 [self updateTag:IDC_STOP]; | 108 [self updateTag:IDC_STOP]; |
| 107 [self setEnabled:YES]; | 109 } else if (force) { |
| 108 } else if (force || ![self isMouseInside]) { | 110 [self invalidatePendingReloadTimer]; |
| 109 pendingReloadTimer_.reset(); | |
| 110 [self updateTag:IDC_RELOAD]; | 111 [self updateTag:IDC_RELOAD]; |
| 111 | 112 } else if ([self tag] == IDC_STOP && |
| 112 // This button's cell may not have received a mouseExited event, and | 113 !pendingReloadTimer_ && |
| 113 // therefore it could still think that the mouse is inside the button. Make | 114 [[self cell] isMouseInside]) { |
| 114 // sure the cell's sense of mouse-inside matches the local sense, to prevent | |
| 115 // drawing artifacts. | |
| 116 id cell = [self cell]; | 115 id cell = [self cell]; |
| 117 if ([cell respondsToSelector:@selector(setIsMouseInside:)]) | 116 [cell setImageID:IDR_STOP_D |
| 118 [cell setIsMouseInside:[self isMouseInside]]; | 117 forButtonState:image_button_cell::kDefaultState]; |
| 119 [self setEnabled:YES]; | 118 [cell setImageID:IDR_STOP_D |
| 120 } else if ([self tag] == IDC_STOP && !pendingReloadTimer_) { | 119 forButtonState:image_button_cell::kDisabledState]; |
| 121 [self setEnabled:NO]; | 120 [cell setImageID:IDR_STOP_D |
| 122 pendingReloadTimer_.reset( | 121 forButtonState:image_button_cell::kHoverState]; |
| 123 [[NSTimer scheduledTimerWithTimeInterval:kPendingReloadTimeout | 122 [cell setImageID:IDR_STOP_D |
| 124 target:self | 123 forButtonState:image_button_cell::kPressedState]; |
| 125 selector:@selector(forceReloadState) | 124 pendingReloadTimer_ = |
| 126 userInfo:nil | 125 [NSTimer timerWithTimeInterval:kPendingReloadTimeout |
| 127 repeats:NO] retain]); | 126 target:self |
| 127 selector:@selector(forceReloadState:) | |
| 128 userInfo:nil | |
| 129 repeats:NO]; | |
| 130 // Must add the timer to |NSRunLoopCommonModes| because | |
| 131 // it should run in |NSEventTrackingRunLoopMode| as well as | |
| 132 // |NSDefaultRunLoopMode|. | |
| 133 [[NSRunLoop currentRunLoop] addTimer:pendingReloadTimer_ | |
| 134 forMode:NSRunLoopCommonModes]; | |
| 135 } else { | |
| 136 [self invalidatePendingReloadTimer]; | |
| 137 [self updateTag:IDC_RELOAD]; | |
| 128 } | 138 } |
| 139 [self setEnabled:pendingReloadTimer_ == nil]; | |
| 129 } | 140 } |
| 130 | 141 |
| 131 - (void)forceReloadState { | 142 - (void)forceReloadState:(NSTimer *)timer { |
| 143 DCHECK_EQ(timer, pendingReloadTimer_); | |
| 132 [self setIsLoading:NO force:YES]; | 144 [self setIsLoading:NO force:YES]; |
| 145 // Verify that |pendingReloadTimer_| is nil so it is not left dangling. | |
| 146 DCHECK(!pendingReloadTimer_); | |
| 133 } | 147 } |
| 134 | 148 |
| 135 - (BOOL)sendAction:(SEL)theAction to:(id)theTarget { | 149 - (BOOL)sendAction:(SEL)theAction to:(id)theTarget { |
| 136 if ([self tag] == IDC_STOP) { | 150 if ([self tag] == IDC_STOP) { |
| 137 // When the timer is started, the button is disabled, so this | 151 if (pendingReloadTimer_) { |
| 138 // should not be possible. | 152 // If |pendingReloadTimer_| then the control is currently being |
| 139 DCHECK(!pendingReloadTimer_.get()); | 153 // drawn in a disabled state, so just return. The control is NOT actually |
| 140 | 154 // disabled, otherwise mousetracking (courtesy of the NSButtonCell) |
| 141 // When the stop is processed, immediately change to reload mode, | 155 // would not work. |
| 142 // even though the IPC still has to bounce off the renderer and | 156 return YES; |
| 143 // back before the regular |-setIsLoaded:force:| will be called. | 157 } else { |
| 144 // [This is how views and gtk do it.] | 158 // When the stop is processed, immediately change to reload mode, |
| 145 const BOOL ret = [super sendAction:theAction to:theTarget]; | 159 // even though the IPC still has to bounce off the renderer and |
| 146 if (ret) | 160 // back before the regular |-setIsLoaded:force:| will be called. |
| 147 [self forceReloadState]; | 161 // [This is how views and gtk do it.] |
| 148 return ret; | 162 BOOL ret = [super sendAction:theAction to:theTarget]; |
| 163 if (ret) | |
| 164 [self forceReloadState:pendingReloadTimer_]; | |
|
Scott Hess - ex-Googler
2011/08/04 23:49:44
This seems contrived (pendingReloadTimer_ is nil,
| |
| 165 return ret; | |
| 166 } | |
| 149 } | 167 } |
| 150 | 168 |
| 151 return [super sendAction:theAction to:theTarget]; | 169 return [super sendAction:theAction to:theTarget]; |
| 152 } | 170 } |
| 153 | 171 |
| 154 - (void)mouseEntered:(NSEvent*)theEvent { | |
| 155 isMouseInside_ = YES; | |
| 156 } | |
| 157 | |
| 158 - (void)mouseExited:(NSEvent*)theEvent { | |
| 159 isMouseInside_ = NO; | |
| 160 | |
| 161 // Reload mode was requested during the hover. | |
| 162 if (pendingReloadTimer_) | |
| 163 [self forceReloadState]; | |
| 164 } | |
| 165 | |
| 166 - (BOOL)isMouseInside { | |
| 167 return isMouseInside_; | |
| 168 } | |
| 169 | |
| 170 - (ViewID)viewID { | 172 - (ViewID)viewID { |
| 171 return VIEW_ID_RELOAD_BUTTON; | 173 return VIEW_ID_RELOAD_BUTTON; |
| 172 } | 174 } |
| 173 | 175 |
| 176 - (void)mouseInsideStateDidChange:(BOOL)isInside { | |
| 177 if (pendingReloadTimer_) | |
| 178 [pendingReloadTimer_ fire]; | |
|
Scott Hess - ex-Googler
2011/08/04 23:49:44
No need for the if(), and this can't possibly happ
| |
| 179 } | |
| 180 | |
| 174 @end // ReloadButton | 181 @end // ReloadButton |
| 175 | 182 |
| 176 @implementation ReloadButton (Testing) | 183 @implementation ReloadButton (Testing) |
| 177 | 184 |
| 178 + (void)setPendingReloadTimeout:(NSTimeInterval)seconds { | 185 + (void)setPendingReloadTimeout:(NSTimeInterval)seconds { |
| 179 kPendingReloadTimeout = seconds; | 186 kPendingReloadTimeout = seconds; |
| 180 } | 187 } |
| 181 | 188 |
| 182 - (NSTrackingArea*)trackingArea { | |
| 183 return trackingArea_; | |
| 184 } | |
| 185 | |
| 186 @end | 189 @end |
| OLD | NEW |