Home | History | Annotate | Download | only in toolbar
      1 // Copyright (c) 2010 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 "ui/base/l10n/l10n_util.h"
      8 #include "app/mac/nsimage_cache.h"
      9 #include "chrome/app/chrome_command_ids.h"
     10 #import "chrome/browser/ui/cocoa/gradient_button_cell.h"
     11 #import "chrome/browser/ui/cocoa/view_id_util.h"
     12 #include "grit/generated_resources.h"
     13 #include "ui/base/l10n/l10n_util_mac.h"
     14 
     15 namespace {
     16 
     17 NSString* const kReloadImageName = @"reload_Template.pdf";
     18 NSString* const kStopImageName = @"stop_Template.pdf";
     19 
     20 // Constant matches Windows.
     21 NSTimeInterval kPendingReloadTimeout = 1.35;
     22 
     23 }  // namespace
     24 
     25 @implementation ReloadButton
     26 
     27 - (void)dealloc {
     28   if (trackingArea_) {
     29     [self removeTrackingArea:trackingArea_];
     30     trackingArea_.reset();
     31   }
     32   [super dealloc];
     33 }
     34 
     35 - (void)updateTrackingAreas {
     36   // If the mouse is hovering when the tracking area is updated, the
     37   // control could end up locked into inappropriate behavior for
     38   // awhile, so unwind state.
     39   if (isMouseInside_)
     40     [self mouseExited:nil];
     41 
     42   if (trackingArea_) {
     43     [self removeTrackingArea:trackingArea_];
     44     trackingArea_.reset();
     45   }
     46   trackingArea_.reset([[NSTrackingArea alloc]
     47                         initWithRect:[self bounds]
     48                              options:(NSTrackingMouseEnteredAndExited |
     49                                       NSTrackingActiveInActiveApp)
     50                                owner:self
     51                             userInfo:nil]);
     52   [self addTrackingArea:trackingArea_];
     53 }
     54 
     55 - (void)awakeFromNib {
     56   [self updateTrackingAreas];
     57 
     58   // Don't allow multi-clicks, because the user probably wouldn't ever
     59   // want to stop+reload or reload+stop.
     60   [self setIgnoresMultiClick:YES];
     61 }
     62 
     63 - (void)updateTag:(NSInteger)anInt {
     64   if ([self tag] == anInt)
     65     return;
     66 
     67   // Forcibly remove any stale tooltip which is being displayed.
     68   [self removeAllToolTips];
     69 
     70   [self setTag:anInt];
     71   if (anInt == IDC_RELOAD) {
     72     [self setImage:app::mac::GetCachedImageWithName(kReloadImageName)];
     73     [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_RELOAD)];
     74   } else if (anInt == IDC_STOP) {
     75     [self setImage:app::mac::GetCachedImageWithName(kStopImageName)];
     76     [self setToolTip:l10n_util::GetNSStringWithFixup(IDS_TOOLTIP_STOP)];
     77   } else {
     78     NOTREACHED();
     79   }
     80 }
     81 
     82 - (void)setIsLoading:(BOOL)isLoading force:(BOOL)force {
     83   // Can always transition to stop mode.  Only transition to reload
     84   // mode if forced or if the mouse isn't hovering.  Otherwise, note
     85   // that reload mode is desired and disable the button.
     86   if (isLoading) {
     87     pendingReloadTimer_.reset();
     88     [self updateTag:IDC_STOP];
     89     [self setEnabled:YES];
     90   } else if (force || ![self isMouseInside]) {
     91     pendingReloadTimer_.reset();
     92     [self updateTag:IDC_RELOAD];
     93 
     94     // This button's cell may not have received a mouseExited event, and
     95     // therefore it could still think that the mouse is inside the button.  Make
     96     // sure the cell's sense of mouse-inside matches the local sense, to prevent
     97     // drawing artifacts.
     98     id cell = [self cell];
     99     if ([cell respondsToSelector:@selector(setMouseInside:animate:)])
    100       [cell setMouseInside:[self isMouseInside] animate:NO];
    101     [self setEnabled:YES];
    102   } else if ([self tag] == IDC_STOP && !pendingReloadTimer_) {
    103     [self setEnabled:NO];
    104     pendingReloadTimer_.reset(
    105         [[NSTimer scheduledTimerWithTimeInterval:kPendingReloadTimeout
    106                                           target:self
    107                                         selector:@selector(forceReloadState)
    108                                         userInfo:nil
    109                                          repeats:NO] retain]);
    110   }
    111 }
    112 
    113 - (void)forceReloadState {
    114   [self setIsLoading:NO force:YES];
    115 }
    116 
    117 - (BOOL)sendAction:(SEL)theAction to:(id)theTarget {
    118   if ([self tag] == IDC_STOP) {
    119     // When the timer is started, the button is disabled, so this
    120     // should not be possible.
    121     DCHECK(!pendingReloadTimer_.get());
    122 
    123     // When the stop is processed, immediately change to reload mode,
    124     // even though the IPC still has to bounce off the renderer and
    125     // back before the regular |-setIsLoaded:force:| will be called.
    126     // [This is how views and gtk do it.]
    127     const BOOL ret = [super sendAction:theAction to:theTarget];
    128     if (ret)
    129       [self forceReloadState];
    130     return ret;
    131   }
    132 
    133   return [super sendAction:theAction to:theTarget];
    134 }
    135 
    136 - (void)mouseEntered:(NSEvent*)theEvent {
    137   isMouseInside_ = YES;
    138 }
    139 
    140 - (void)mouseExited:(NSEvent*)theEvent {
    141   isMouseInside_ = NO;
    142 
    143   // Reload mode was requested during the hover.
    144   if (pendingReloadTimer_)
    145     [self forceReloadState];
    146 }
    147 
    148 - (BOOL)isMouseInside {
    149   return isMouseInside_;
    150 }
    151 
    152 - (ViewID)viewID {
    153   return VIEW_ID_RELOAD_BUTTON;
    154 }
    155 
    156 @end  // ReloadButton
    157 
    158 @implementation ReloadButton (Testing)
    159 
    160 + (void)setPendingReloadTimeout:(NSTimeInterval)seconds {
    161   kPendingReloadTimeout = seconds;
    162 }
    163 
    164 - (NSTrackingArea*)trackingArea {
    165   return trackingArea_;
    166 }
    167 
    168 @end
    169