| OLD | NEW |
| (Empty) |
| 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. | |
| 4 | |
| 5 #import "chrome/browser/ui/cocoa/toolbar/reload_button.h" | |
| 6 | |
| 7 #include "chrome/app/chrome_command_ids.h" | |
| 8 #import "chrome/browser/ui/cocoa/accelerators_cocoa.h" | |
| 9 #import "chrome/browser/ui/cocoa/view_id_util.h" | |
| 10 #include "chrome/browser/command_updater.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" | |
| 17 | |
| 18 namespace { | |
| 19 | |
| 20 // Constant matches Windows. | |
| 21 NSTimeInterval kPendingReloadTimeout = 1.35; | |
| 22 | |
| 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, | |
| 28 }; | |
| 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, | |
| 34 }; | |
| 35 | |
| 36 } // namespace | |
| 37 | |
| 38 @interface ReloadButton () | |
| 39 - (void)invalidatePendingReloadTimer; | |
| 40 - (void)forceReloadState:(NSTimer *)timer; | |
| 41 - (void)populateMenu; | |
| 42 @end | |
| 43 | |
| 44 @implementation ReloadButton | |
| 45 | |
| 46 + (Class)cellClass { | |
| 47 return [ClickHoldButtonCell class]; | |
| 48 } | |
| 49 | |
| 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; | |
| 56 } | |
| 57 | |
| 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]; | |
| 62 } | |
| 63 | |
| 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]; | |
| 68 | |
| 69 [self setOpenMenuOnRightClick:YES]; | |
| 70 [self setOpenMenuOnClick:NO]; | |
| 71 | |
| 72 menu_.reset([[NSMenu alloc] initWithTitle:@""]); | |
| 73 [self populateMenu]; | |
| 74 } | |
| 75 | |
| 76 - (void)invalidatePendingReloadTimer { | |
| 77 [pendingReloadTimer_ invalidate]; | |
| 78 pendingReloadTimer_ = nil; | |
| 79 } | |
| 80 | |
| 81 - (void)updateTag:(NSInteger)anInt { | |
| 82 if ([self tag] == anInt) | |
| 83 return; | |
| 84 | |
| 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 } | |
| 114 } | |
| 115 | |
| 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 } | |
| 123 } | |
| 124 | |
| 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]; | |
| 163 } | |
| 164 | |
| 165 - (void)setMenuEnabled:(BOOL)enabled { | |
| 166 [self setAttachedMenu:(enabled ? menu_ : nil)]; | |
| 167 } | |
| 168 | |
| 169 - (void)setCommandUpdater:(CommandUpdater*)commandUpdater { | |
| 170 commandUpdater_ = commandUpdater; | |
| 171 } | |
| 172 | |
| 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_); | |
| 178 } | |
| 179 | |
| 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 } | |
| 199 | |
| 200 return [super sendAction:theAction to:theTarget]; | |
| 201 } | |
| 202 | |
| 203 - (ViewID)viewID { | |
| 204 return VIEW_ID_RELOAD_BUTTON; | |
| 205 } | |
| 206 | |
| 207 - (void)mouseInsideStateDidChange:(BOOL)isInside { | |
| 208 [pendingReloadTimer_ fire]; | |
| 209 } | |
| 210 | |
| 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:@""]); | |
| 225 | |
| 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 } | |
| 237 | |
| 238 [item setTag:kReloadMenuCommands[i]]; | |
| 239 [item setTarget:self]; | |
| 240 [item setEnabled:YES]; | |
| 241 | |
| 242 [menu_ addItem:item]; | |
| 243 } | |
| 244 } | |
| 245 | |
| 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)); | |
| 255 } | |
| 256 | |
| 257 @end // ReloadButton | |
| 258 | |
| 259 @implementation ReloadButton (Testing) | |
| 260 | |
| 261 + (void)setPendingReloadTimeout:(NSTimeInterval)seconds { | |
| 262 kPendingReloadTimeout = seconds; | |
| 263 } | |
| 264 | |
| 265 @end | |
| OLD | NEW |