Home | History | Annotate | Download | only in toolbar
      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/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"
     13 
     14 namespace {
     15 
     16 // Constant matches Windows.
     17 NSTimeInterval kPendingReloadTimeout = 1.35;
     18 
     19 }  // namespace
     20 
     21 @interface ReloadButton ()
     22 - (void)invalidatePendingReloadTimer;
     23 - (void)forceReloadState:(NSTimer *)timer;
     24 @end
     25 
     26 @implementation ReloadButton
     27 
     28 + (Class)cellClass {
     29   return [ImageButtonCell class];
     30 }
     31 
     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.
     35     [self awakeFromNib];
     36   }
     37   return self;
     38 }
     39 
     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];
     44 }
     45 
     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];
     50 }
     51 
     52 - (void)invalidatePendingReloadTimer {
     53   [pendingReloadTimer_ invalidate];
     54   pendingReloadTimer_ = nil;
     55 }
     56 
     57 - (void)updateTag:(NSInteger)anInt {
     58   if ([self tag] == anInt)
     59     return;
     60 
     61   // Forcibly remove any stale tooltip which is being displayed.
     62   [self removeAllToolTips];
     63   id cell = [self cell];
     64   [self setTag:anInt];
     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.
     74     [cell setImageID: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)];
     87   } else {
     88     NOTREACHED();
     89   }
     90 }
     91 
     92 - (id)accessibilityAttributeValue:(NSString *)attribute {
     93   if ([attribute isEqualToString:NSAccessibilityEnabledAttribute] &&
     94       pendingReloadTimer_) {
     95     return [NSNumber numberWithBool:NO];
     96   } else {
     97     return [super accessibilityAttributeValue:attribute];
     98   }
     99 }
    100 
    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.
    105   if (isLoading) {
    106     [self invalidatePendingReloadTimer];
    107     [self updateTag:IDC_STOP];
    108   } else if (force) {
    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
    125                                 target:self
    126                               selector:@selector(forceReloadState:)
    127                               userInfo:nil
    128                                repeats:NO];
    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];
    134   } else {
    135     [self invalidatePendingReloadTimer];
    136     [self updateTag:IDC_RELOAD];
    137   }
    138   [self setEnabled:pendingReloadTimer_ == nil];
    139 }
    140 
    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_);
    146 }
    147 
    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)
    154       // would not work.
    155       return YES;
    156     } else {
    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];
    162       if (ret)
    163         [self forceReloadState:pendingReloadTimer_];
    164       return ret;
    165     }
    166   }
    167 
    168   return [super sendAction:theAction to:theTarget];
    169 }
    170 
    171 - (ViewID)viewID {
    172   return VIEW_ID_RELOAD_BUTTON;
    173 }
    174 
    175 - (void)mouseInsideStateDidChange:(BOOL)isInside {
    176   [pendingReloadTimer_ fire];
    177 }
    178 
    179 @end  // ReloadButton
    180 
    181 @implementation ReloadButton (Testing)
    182 
    183 + (void)setPendingReloadTimeout:(NSTimeInterval)seconds {
    184   kPendingReloadTimeout = seconds;
    185 }
    186 
    187 @end
    188