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 |