• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
15namespace {
16
17NSString* const kReloadImageName = @"reload_Template.pdf";
18NSString* const kStopImageName = @"stop_Template.pdf";
19
20// Constant matches Windows.
21NSTimeInterval 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