Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / toolbar / reload_button_cocoa.mm
blob67c1b58fba8fa47a42af34d1c6bfa230331f001e
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_cocoa.h"
7 #include "chrome/app/chrome_command_ids.h"
8 #include "chrome/browser/command_updater.h"
9 #import "chrome/browser/ui/cocoa/accelerators_cocoa.h"
10 #import "chrome/browser/ui/cocoa/view_id_util.h"
11 #include "chrome/grit/generated_resources.h"
12 #include "grit/theme_resources.h"
13 #include "ui/base/accelerators/platform_accelerator_cocoa.h"
14 #include "ui/base/l10n/l10n_util.h"
15 #include "ui/base/l10n/l10n_util_mac.h"
16 #import "ui/events/event_utils.h"
18 namespace {
20 // Constant matches Windows.
21 NSTimeInterval kPendingReloadTimeout = 1.35;
23 // Contents of the Reload drop-down menu.
24 const int kReloadMenuItems[]  = {
25   IDS_RELOAD_MENU_NORMAL_RELOAD_ITEM,
26   IDS_RELOAD_MENU_HARD_RELOAD_ITEM,
27   IDS_RELOAD_MENU_EMPTY_AND_HARD_RELOAD_ITEM,
29 // Note: must have the same size as |kReloadMenuItems|.
30 const int kReloadMenuCommands[]  = {
31   IDC_RELOAD,
32   IDC_RELOAD_IGNORING_CACHE,
33   IDC_RELOAD_CLEARING_CACHE,
36 }  // namespace
38 @interface ReloadButton ()
39 - (void)invalidatePendingReloadTimer;
40 - (void)forceReloadState:(NSTimer *)timer;
41 - (void)populateMenu;
42 @end
44 @implementation ReloadButton
46 + (Class)cellClass {
47   return [ClickHoldButtonCell class];
50 - (id)initWithFrame:(NSRect)frameRect {
51   if ((self = [super initWithFrame:frameRect])) {
52     // Since this is not a custom view, -awakeFromNib won't be called twice.
53     [self awakeFromNib];
54   }
55   return self;
58 - (void)viewWillMoveToWindow:(NSWindow *)newWindow {
59   // If this view is moved to a new window, reset its state.
60   [self setIsLoading:NO force:YES];
61   [super viewWillMoveToWindow:newWindow];
64 - (void)awakeFromNib {
65   // Don't allow multi-clicks, because the user probably wouldn't ever
66   // want to stop+reload or reload+stop.
67   [self setIgnoresMultiClick:YES];
69   [self setOpenMenuOnRightClick:YES];
70   [self setOpenMenuOnClick:NO];
72   menu_.reset([[NSMenu alloc] initWithTitle:@""]);
73   [self populateMenu];
76 - (void)invalidatePendingReloadTimer {
77   [pendingReloadTimer_ invalidate];
78   pendingReloadTimer_ = nil;
81 - (void)updateTag:(NSInteger)anInt {
82   if ([self tag] == anInt)
83     return;
85   // Forcibly remove any stale tooltip which is being displayed.
86   [self removeAllToolTips];
87   id cell = [self cell];
88   [self setTag:anInt];
89   if (anInt == IDC_RELOAD) {
90     [cell setImageID:IDR_RELOAD
91       forButtonState:image_button_cell::kDefaultState];
92     [cell setImageID:IDR_RELOAD_H
93       forButtonState:image_button_cell::kHoverState];
94     [cell setImageID:IDR_RELOAD_P
95       forButtonState:image_button_cell::kPressedState];
96     // The stop button has a disabled image but the reload button doesn't. To
97     // unset it we have to explicilty change the image ID to 0.
98     [cell setImageID:0
99       forButtonState:image_button_cell::kDisabledState];
100     [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_RELOAD)];
101   } else if (anInt == IDC_STOP) {
102     [cell setImageID:IDR_STOP
103       forButtonState:image_button_cell::kDefaultState];
104     [cell setImageID:IDR_STOP_H
105       forButtonState:image_button_cell::kHoverState];
106     [cell setImageID:IDR_STOP_P
107       forButtonState:image_button_cell::kPressedState];
108     [cell setImageID:IDR_STOP_D
109       forButtonState:image_button_cell::kDisabledState];
110     [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_STOP)];
111   } else {
112     NOTREACHED();
113   }
116 - (id)accessibilityAttributeValue:(NSString *)attribute {
117   if ([attribute isEqualToString:NSAccessibilityEnabledAttribute] &&
118       pendingReloadTimer_) {
119     return [NSNumber numberWithBool:NO];
120   } else {
121     return [super accessibilityAttributeValue:attribute];
122   }
125 - (void)setIsLoading:(BOOL)isLoading force:(BOOL)force {
126   // Can always transition to stop mode.  Only transition to reload
127   // mode if forced or if the mouse isn't hovering.  Otherwise, note
128   // that reload mode is desired and disable the button.
129   if (isLoading) {
130     [self invalidatePendingReloadTimer];
131     [self updateTag:IDC_STOP];
132   } else if (force) {
133     [self invalidatePendingReloadTimer];
134     [self updateTag:IDC_RELOAD];
135   } else if ([self tag] == IDC_STOP &&
136              !pendingReloadTimer_ &&
137              [[self cell] isMouseInside]) {
138     id cell = [self cell];
139     [cell setImageID:IDR_STOP_D
140       forButtonState:image_button_cell::kDefaultState];
141     [cell setImageID:IDR_STOP_D
142       forButtonState:image_button_cell::kDisabledState];
143     [cell setImageID:IDR_STOP_D
144       forButtonState:image_button_cell::kHoverState];
145     [cell setImageID:IDR_STOP_D
146       forButtonState:image_button_cell::kPressedState];
147     pendingReloadTimer_ =
148         [NSTimer timerWithTimeInterval:kPendingReloadTimeout
149                                 target:self
150                               selector:@selector(forceReloadState:)
151                               userInfo:nil
152                                repeats:NO];
153     // Must add the timer to |NSRunLoopCommonModes| because
154     // it should run in |NSEventTrackingRunLoopMode| as well as
155     // |NSDefaultRunLoopMode|.
156     [[NSRunLoop currentRunLoop] addTimer:pendingReloadTimer_
157                                  forMode:NSRunLoopCommonModes];
158   } else {
159     [self invalidatePendingReloadTimer];
160     [self updateTag:IDC_RELOAD];
161   }
162   [self setEnabled:pendingReloadTimer_ == nil];
165 - (void)setMenuEnabled:(BOOL)enabled {
166   [self setAttachedMenu:(enabled ? menu_ : nil)];
169 - (void)setCommandUpdater:(CommandUpdater*)commandUpdater {
170   commandUpdater_ = commandUpdater;
173 - (void)forceReloadState:(NSTimer *)timer {
174   DCHECK_EQ(timer, pendingReloadTimer_);
175   [self setIsLoading:NO force:YES];
176   // Verify that |pendingReloadTimer_| is nil so it is not left dangling.
177   DCHECK(!pendingReloadTimer_);
180 - (BOOL)sendAction:(SEL)theAction to:(id)theTarget {
181   if ([self tag] == IDC_STOP) {
182     if (pendingReloadTimer_) {
183       // If |pendingReloadTimer_| then the control is currently being
184       // drawn in a disabled state, so just return. The control is NOT actually
185       // disabled, otherwise mousetracking (courtesy of the NSButtonCell)
186       // would not work.
187       return YES;
188     } else {
189       // When the stop is processed, immediately change to reload mode,
190       // even though the IPC still has to bounce off the renderer and
191       // back before the regular |-setIsLoaded:force:| will be called.
192       // [This is how views and gtk do it.]
193       BOOL ret = [super sendAction:theAction to:theTarget];
194       if (ret)
195         [self forceReloadState:pendingReloadTimer_];
196       return ret;
197     }
198   }
200   return [super sendAction:theAction to:theTarget];
203 - (ViewID)viewID {
204   return VIEW_ID_RELOAD_BUTTON;
207 - (void)mouseInsideStateDidChange:(BOOL)isInside {
208   [pendingReloadTimer_ fire];
211 - (void)populateMenu {
212   [menu_ setAutoenablesItems:NO];
213   // 0-th item must be blank. (This is because we use a pulldown list, for which
214   // Cocoa uses the 0-th item as "title" in the button.)
215   [menu_ addItemWithTitle:@""
216                    action:nil
217             keyEquivalent:@""];
218   AcceleratorsCocoa* keymap = AcceleratorsCocoa::GetInstance();
219   for (size_t i = 0; i < arraysize(kReloadMenuItems); ++i) {
220     NSString* title = l10n_util::GetNSStringWithFixup(kReloadMenuItems[i]);
221     base::scoped_nsobject<NSMenuItem> item(
222         [[NSMenuItem alloc] initWithTitle:title
223                                    action:@selector(executeMenuItem:)
224                             keyEquivalent:@""]);
226     const ui::Accelerator* accelerator =
227         keymap->GetAcceleratorForCommand(kReloadMenuCommands[i]);
228     if (accelerator) {
229       const ui::PlatformAcceleratorCocoa* platform =
230           static_cast<const ui::PlatformAcceleratorCocoa*>(
231                 accelerator->platform_accelerator());
232       if (platform) {
233         [item setKeyEquivalent:platform->characters()];
234         [item setKeyEquivalentModifierMask:platform->modifier_mask()];
235       }
236     }
238     [item setTag:kReloadMenuCommands[i]];
239     [item setTarget:self];
240     [item setEnabled:YES];
242     [menu_ addItem:item];
243   }
246 // Action for menu items.
247 - (void)executeMenuItem:(id)sender {
248   if (!commandUpdater_)
249     return;
250   DCHECK([sender isKindOfClass:[NSMenuItem class]]);
251   int command = [sender tag];
252   int event_flags = ui::EventFlagsFromNative([NSApp currentEvent]);
253   commandUpdater_->ExecuteCommandWithDisposition(
254       command, ui::DispositionFromEventFlags(event_flags));
257 @end  // ReloadButton
259 @implementation ReloadButton (Testing)
261 + (void)setPendingReloadTimeout:(NSTimeInterval)seconds {
262   kPendingReloadTimeout = seconds;
265 @end