1 // Copyright (c) 2012 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.
5 #import "chrome/browser/ui/cocoa/toolbar/reload_button.h"
7 #include "chrome/app/chrome_command_ids.h"
8 #import "chrome/browser/ui/cocoa/view_id_util.h"
9 #include "grit/generated_resources.h"
10 #include "grit/theme_resources.h"
11 #include "ui/base/l10n/l10n_util.h"
12 #include "ui/base/l10n/l10n_util_mac.h"
16 // Constant matches Windows.
17 NSTimeInterval kPendingReloadTimeout = 1.35;
21 @interface ReloadButton ()
22 - (void)invalidatePendingReloadTimer;
23 - (void)forceReloadState:(NSTimer *)timer;
26 @implementation ReloadButton
29 return [ImageButtonCell class];
32 - (id)initWithFrame:(NSRect)frameRect {
33 if ((self = [super initWithFrame:frameRect])) {
34 // Since this is not a custom view, -awakeFromNib won't be called twice.
40 - (void)viewWillMoveToWindow:(NSWindow *)newWindow {
41 // If this view is moved to a new window, reset its state.
42 [self setIsLoading:NO force:YES];
43 [super viewWillMoveToWindow:newWindow];
46 - (void)awakeFromNib {
47 // Don't allow multi-clicks, because the user probably wouldn't ever
48 // want to stop+reload or reload+stop.
49 [self setIgnoresMultiClick:YES];
52 - (void)invalidatePendingReloadTimer {
53 [pendingReloadTimer_ invalidate];
54 pendingReloadTimer_ = nil;
57 - (void)updateTag:(NSInteger)anInt {
58 if ([self tag] == anInt)
61 // Forcibly remove any stale tooltip which is being displayed.
62 [self removeAllToolTips];
63 id cell = [self cell];
65 if (anInt == IDC_RELOAD) {
66 [cell setImageID:IDR_RELOAD
67 forButtonState:image_button_cell::kDefaultState];
68 [cell setImageID:IDR_RELOAD_H
69 forButtonState:image_button_cell::kHoverState];
70 [cell setImageID:IDR_RELOAD_P
71 forButtonState:image_button_cell::kPressedState];
72 // The stop button has a disabled image but the reload button doesn't. To
73 // unset it we have to explicilty change the image ID to 0.
75 forButtonState:image_button_cell::kDisabledState];
76 [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_RELOAD)];
77 } else if (anInt == IDC_STOP) {
78 [cell setImageID:IDR_STOP
79 forButtonState:image_button_cell::kDefaultState];
80 [cell setImageID:IDR_STOP_H
81 forButtonState:image_button_cell::kHoverState];
82 [cell setImageID:IDR_STOP_P
83 forButtonState:image_button_cell::kPressedState];
84 [cell setImageID:IDR_STOP_D
85 forButtonState:image_button_cell::kDisabledState];
86 [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_STOP)];
92 - (id)accessibilityAttributeValue:(NSString *)attribute {
93 if ([attribute isEqualToString:NSAccessibilityEnabledAttribute] &&
94 pendingReloadTimer_) {
95 return [NSNumber numberWithBool:NO];
97 return [super accessibilityAttributeValue:attribute];
101 - (void)setIsLoading:(BOOL)isLoading force:(BOOL)force {
102 // Can always transition to stop mode. Only transition to reload
103 // mode if forced or if the mouse isn't hovering. Otherwise, note
104 // that reload mode is desired and disable the button.
106 [self invalidatePendingReloadTimer];
107 [self updateTag:IDC_STOP];
109 [self invalidatePendingReloadTimer];
110 [self updateTag:IDC_RELOAD];
111 } else if ([self tag] == IDC_STOP &&
112 !pendingReloadTimer_ &&
113 [[self cell] isMouseInside]) {
114 id cell = [self cell];
115 [cell setImageID:IDR_STOP_D
116 forButtonState:image_button_cell::kDefaultState];
117 [cell setImageID:IDR_STOP_D
118 forButtonState:image_button_cell::kDisabledState];
119 [cell setImageID:IDR_STOP_D
120 forButtonState:image_button_cell::kHoverState];
121 [cell setImageID:IDR_STOP_D
122 forButtonState:image_button_cell::kPressedState];
123 pendingReloadTimer_ =
124 [NSTimer timerWithTimeInterval:kPendingReloadTimeout
126 selector:@selector(forceReloadState:)
129 // Must add the timer to |NSRunLoopCommonModes| because
130 // it should run in |NSEventTrackingRunLoopMode| as well as
131 // |NSDefaultRunLoopMode|.
132 [[NSRunLoop currentRunLoop] addTimer:pendingReloadTimer_
133 forMode:NSRunLoopCommonModes];
135 [self invalidatePendingReloadTimer];
136 [self updateTag:IDC_RELOAD];
138 [self setEnabled:pendingReloadTimer_ == nil];
141 - (void)forceReloadState:(NSTimer *)timer {
142 DCHECK_EQ(timer, pendingReloadTimer_);
143 [self setIsLoading:NO force:YES];
144 // Verify that |pendingReloadTimer_| is nil so it is not left dangling.
145 DCHECK(!pendingReloadTimer_);
148 - (BOOL)sendAction:(SEL)theAction to:(id)theTarget {
149 if ([self tag] == IDC_STOP) {
150 if (pendingReloadTimer_) {
151 // If |pendingReloadTimer_| then the control is currently being
152 // drawn in a disabled state, so just return. The control is NOT actually
153 // disabled, otherwise mousetracking (courtesy of the NSButtonCell)
157 // When the stop is processed, immediately change to reload mode,
158 // even though the IPC still has to bounce off the renderer and
159 // back before the regular |-setIsLoaded:force:| will be called.
160 // [This is how views and gtk do it.]
161 BOOL ret = [super sendAction:theAction to:theTarget];
163 [self forceReloadState:pendingReloadTimer_];
168 return [super sendAction:theAction to:theTarget];
172 return VIEW_ID_RELOAD_BUTTON;
175 - (void)mouseInsideStateDidChange:(BOOL)isInside {
176 [pendingReloadTimer_ fire];
181 @implementation ReloadButton (Testing)
183 + (void)setPendingReloadTimeout:(NSTimeInterval)seconds {
184 kPendingReloadTimeout = seconds;