Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(559)

Side by Side Diff: chrome/browser/ui/cocoa/toolbar/reload_button.mm

Issue 7466016: Fix up reload button to not flicker, and simplify implementation. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: remove unnecessary header Created 9 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698