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)commonInit; | |
Scott Hess - ex-Googler
2011/07/21 00:48:40
-sharedInit is used in two cases, -commonInit is u
dmac
2011/07/29 20:01:00
Not sure what you meant by the anonymous namespace
Scott Hess - ex-Googler
2011/08/04 23:49:44
Mostly I meant that hidden objc methods like this
| |
24 - (void)invalidatePendingReloadTimer; | |
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 [self commonInit]; |
32 trackingArea_.reset(); | |
33 } | 36 } |
34 [super dealloc]; | 37 return self; |
35 } | 38 } |
36 | 39 |
37 - (void)updateTrackingAreas { | 40 - (void)viewWillMoveToWindow:(NSWindow *)newWindow { |
38 // If the mouse is hovering when the tracking area is updated, the | 41 // Since |pendingReloadTimer_| retains |self|, it should be invalidated |
39 // control could end up locked into inappropriate behavior for | 42 // if it is active when the view is moved. |newWindow| is nil when |self| |
40 // awhile, so unwind state. | 43 // is removed from the view hierarchy. |
Scott Hess - ex-Googler
2011/07/21 00:48:40
Is this being added for a specific reason? I can
dmac
2011/07/29 20:01:00
Enforces a good state whereever the view is placed
| |
41 if (isMouseInside_) | 44 [self invalidatePendingReloadTimer]; |
42 [self mouseExited:nil]; | 45 [super viewWillMoveToWindow:newWindow]; |
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 } | 46 } |
56 | 47 |
57 - (void)awakeFromNib { | 48 - (void)awakeFromNib { |
58 [self updateTrackingAreas]; | 49 [self commonInit]; |
50 } | |
59 | 51 |
52 - (void)commonInit { | |
60 // Don't allow multi-clicks, because the user probably wouldn't ever | 53 // Don't allow multi-clicks, because the user probably wouldn't ever |
61 // want to stop+reload or reload+stop. | 54 // want to stop+reload or reload+stop. |
62 [self setIgnoresMultiClick:YES]; | 55 [self setIgnoresMultiClick:YES]; |
63 } | 56 } |
64 | 57 |
58 - (void)invalidatePendingReloadTimer { | |
59 [pendingReloadTimer_ invalidate]; | |
60 pendingReloadTimer_ = nil; | |
61 } | |
62 | |
65 - (void)updateTag:(NSInteger)anInt { | 63 - (void)updateTag:(NSInteger)anInt { |
66 if ([self tag] == anInt) | 64 if ([self tag] == anInt) |
67 return; | 65 return; |
68 | 66 |
69 // Forcibly remove any stale tooltip which is being displayed. | 67 // Forcibly remove any stale tooltip which is being displayed. |
70 [self removeAllToolTips]; | 68 [self removeAllToolTips]; |
71 | 69 id cell = [self cell]; |
72 [self setTag:anInt]; | 70 [self setTag:anInt]; |
73 if (anInt == IDC_RELOAD) { | 71 if (anInt == IDC_RELOAD) { |
74 [[self cell] setImageID:IDR_RELOAD | 72 [cell setImageID:IDR_RELOAD |
75 forButtonState:image_button_cell::kDefaultState]; | 73 forButtonState:image_button_cell::kDefaultState]; |
76 [[self cell] setImageID:IDR_RELOAD_H | 74 [cell setImageID:IDR_RELOAD_H |
77 forButtonState:image_button_cell::kHoverState]; | 75 forButtonState:image_button_cell::kHoverState]; |
78 [[self cell] setImageID:IDR_RELOAD_P | 76 [cell setImageID:IDR_RELOAD_P |
79 forButtonState:image_button_cell::kPressedState]; | 77 forButtonState:image_button_cell::kPressedState]; |
80 // The stop button has a disabled image but the reload button doesn't. To | 78 // 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. | 79 // unset it we have to explicilty change the image ID to 0. |
82 [[self cell] setImageID:0 | 80 [cell setImageID:0 |
83 forButtonState:image_button_cell::kDisabledState]; | 81 forButtonState:image_button_cell::kDisabledState]; |
84 [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_RELOAD)]; | 82 [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_RELOAD)]; |
85 } else if (anInt == IDC_STOP) { | 83 } else if (anInt == IDC_STOP) { |
86 [[self cell] setImageID:IDR_STOP | 84 [cell setImageID:IDR_STOP |
87 forButtonState:image_button_cell::kDefaultState]; | 85 forButtonState:image_button_cell::kDefaultState]; |
88 [[self cell] setImageID:IDR_STOP_H | 86 [cell setImageID:IDR_STOP_H |
89 forButtonState:image_button_cell::kHoverState]; | 87 forButtonState:image_button_cell::kHoverState]; |
90 [[self cell] setImageID:IDR_STOP_P | 88 [cell setImageID:IDR_STOP_P |
91 forButtonState:image_button_cell::kPressedState]; | 89 forButtonState:image_button_cell::kPressedState]; |
92 [[self cell] setImageID:IDR_STOP_D | 90 [cell setImageID:IDR_STOP_D |
93 forButtonState:image_button_cell::kDisabledState]; | 91 forButtonState:image_button_cell::kDisabledState]; |
94 [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_STOP)]; | 92 [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_STOP)]; |
95 } else { | 93 } else { |
96 NOTREACHED(); | 94 NOTREACHED(); |
97 } | 95 } |
98 } | 96 } |
99 | 97 |
100 - (void)setIsLoading:(BOOL)isLoading force:(BOOL)force { | 98 - (void)setIsLoading:(BOOL)isLoading force:(BOOL)force { |
101 // Can always transition to stop mode. Only transition to reload | 99 // Can always transition to stop mode. Only transition to reload |
102 // mode if forced or if the mouse isn't hovering. Otherwise, note | 100 // mode if forced or if the mouse isn't hovering. Otherwise, note |
103 // that reload mode is desired and disable the button. | 101 // that reload mode is desired and disable the button. |
104 if (isLoading) { | 102 if (isLoading) { |
105 pendingReloadTimer_.reset(); | 103 [self invalidatePendingReloadTimer]; |
106 [self updateTag:IDC_STOP]; | 104 [self updateTag:IDC_STOP]; |
107 [self setEnabled:YES]; | 105 } else if (force) { |
108 } else if (force || ![self isMouseInside]) { | 106 [self invalidatePendingReloadTimer]; |
109 pendingReloadTimer_.reset(); | |
110 [self updateTag:IDC_RELOAD]; | 107 [self updateTag:IDC_RELOAD]; |
Scott Hess - ex-Googler
2011/07/21 00:48:40
I _think_ that given the final else clause you add
dmac
2011/07/29 20:01:00
This appears to be working for me (after pretty ex
| |
111 | 108 } else if ([self tag] == IDC_STOP && |
112 // This button's cell may not have received a mouseExited event, and | 109 !pendingReloadTimer_ && |
113 // therefore it could still think that the mouse is inside the button. Make | 110 [[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]; | 111 id cell = [self cell]; |
117 if ([cell respondsToSelector:@selector(setIsMouseInside:)]) | 112 [cell setImageID:IDR_STOP_D |
118 [cell setIsMouseInside:[self isMouseInside]]; | 113 forButtonState:image_button_cell::kDefaultState]; |
119 [self setEnabled:YES]; | 114 [cell setImageID:IDR_STOP_D |
120 } else if ([self tag] == IDC_STOP && !pendingReloadTimer_) { | 115 forButtonState:image_button_cell::kDisabledState]; |
121 [self setEnabled:NO]; | 116 [cell setImageID:IDR_STOP_D |
122 pendingReloadTimer_.reset( | 117 forButtonState:image_button_cell::kHoverState]; |
123 [[NSTimer scheduledTimerWithTimeInterval:kPendingReloadTimeout | 118 [cell setImageID:IDR_STOP_D |
124 target:self | 119 forButtonState:image_button_cell::kPressedState]; |
125 selector:@selector(forceReloadState) | 120 pendingReloadTimer_ = |
126 userInfo:nil | 121 [NSTimer timerWithTimeInterval:kPendingReloadTimeout |
127 repeats:NO] retain]); | 122 target:self |
123 selector:@selector(forceReloadState) | |
124 userInfo:nil | |
125 repeats:NO]; | |
126 // Must add the timer to |NSRunLoopCommonModes| because | |
127 // it should run in |NSEventTrackingRunLoopMode| as well as | |
128 // |NSDefaultRunLoopMode|. | |
129 [[NSRunLoop currentRunLoop] addTimer:pendingReloadTimer_ | |
130 forMode:NSRunLoopCommonModes]; | |
131 } else { | |
132 [self invalidatePendingReloadTimer]; | |
133 [self updateTag:IDC_RELOAD]; | |
128 } | 134 } |
129 } | 135 } |
130 | 136 |
137 - (BOOL)isEnabled { | |
138 return [super isEnabled] && !([self tag] == IDC_STOP && pendingReloadTimer_); | |
Scott Hess - ex-Googler
2011/07/21 00:48:40
pendingReloadTimer_ should imply [self tag] == IDC
dmac
2011/07/29 20:01:00
No longer needed due to mouse tracking having been
| |
139 } | |
140 | |
131 - (void)forceReloadState { | 141 - (void)forceReloadState { |
142 [self invalidatePendingReloadTimer]; | |
Scott Hess - ex-Googler
2011/07/21 00:48:40
-setIsLoading:force: already handles the timer.
dmac
2011/07/29 20:01:00
Done, and added a bit more checking.
| |
132 [self setIsLoading:NO force:YES]; | 143 [self setIsLoading:NO force:YES]; |
133 } | 144 } |
134 | 145 |
135 - (BOOL)sendAction:(SEL)theAction to:(id)theTarget { | 146 - (BOOL)sendAction:(SEL)theAction to:(id)theTarget { |
136 if ([self tag] == IDC_STOP) { | 147 if ([self tag] == IDC_STOP) { |
137 // When the timer is started, the button is disabled, so this | 148 if (pendingReloadTimer_) { |
138 // should not be possible. | 149 return YES; |
Scott Hess - ex-Googler
2011/07/21 00:48:40
In the old code, this couldn't happen because the
dmac
2011/07/29 20:01:00
isEnabled has been removed.
| |
139 DCHECK(!pendingReloadTimer_.get()); | 150 } else { |
140 | 151 // When the stop is processed, immediately change to reload mode, |
141 // When the stop is processed, immediately change to reload mode, | 152 // even though the IPC still has to bounce off the renderer and |
142 // even though the IPC still has to bounce off the renderer and | 153 // back before the regular |-setIsLoaded:force:| will be called. |
143 // back before the regular |-setIsLoaded:force:| will be called. | 154 // [This is how views and gtk do it.] |
144 // [This is how views and gtk do it.] | 155 const BOOL ret = [super sendAction:theAction to:theTarget]; |
145 const BOOL ret = [super sendAction:theAction to:theTarget]; | 156 if (ret) |
146 if (ret) | 157 [self forceReloadState]; |
147 [self forceReloadState]; | 158 return ret; |
148 return ret; | 159 } |
149 } | 160 } |
150 | 161 |
151 return [super sendAction:theAction to:theTarget]; | 162 return [super sendAction:theAction to:theTarget]; |
152 } | 163 } |
153 | 164 |
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 { | 165 - (ViewID)viewID { |
171 return VIEW_ID_RELOAD_BUTTON; | 166 return VIEW_ID_RELOAD_BUTTON; |
172 } | 167 } |
173 | 168 |
169 - (void)mouseInsideStateDidChange:(BOOL)isInside { | |
170 if (!isInside && pendingReloadTimer_) { | |
171 [self forceReloadState]; | |
172 } | |
Scott Hess - ex-Googler
2011/07/21 00:48:40
pendingReloadTimer_ implies that the new state is
dmac
2011/07/29 20:01:00
Done. Good call.
| |
173 } | |
174 | |
174 @end // ReloadButton | 175 @end // ReloadButton |
175 | 176 |
176 @implementation ReloadButton (Testing) | 177 @implementation ReloadButton (Testing) |
177 | 178 |
178 + (void)setPendingReloadTimeout:(NSTimeInterval)seconds { | 179 + (void)setPendingReloadTimeout:(NSTimeInterval)seconds { |
179 kPendingReloadTimeout = seconds; | 180 kPendingReloadTimeout = seconds; |
180 } | 181 } |
181 | 182 |
182 - (NSTrackingArea*)trackingArea { | |
183 return trackingArea_; | |
184 } | |
185 | |
186 @end | 183 @end |
OLD | NEW |