summaryrefslogtreecommitdiffstats
path: root/chrome
diff options
context:
space:
mode:
authorpinkerton@chromium.org <pinkerton@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-19 20:46:37 +0000
committerpinkerton@chromium.org <pinkerton@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-05-19 20:46:37 +0000
commit1ae99501e0a15b85f0593a71c939063027c802d4 (patch)
treeae2c255da0ff4687c78b2a017dd1b660a8ed343c /chrome
parentf8fc92385be0fb12cfffd14db9c6f3b461bec447 (diff)
downloadchromium_src-1ae99501e0a15b85f0593a71c939063027c802d4.zip
chromium_src-1ae99501e0a15b85f0593a71c939063027c802d4.tar.gz
chromium_src-1ae99501e0a15b85f0593a71c939063027c802d4.tar.bz2
Implement a status throbber on the mac, currently using the Win artwork. Made the tab cell use a generic NSView for showing the icon instead of relying on the NSButtonCell to draw it, so a NSImageView is in place by default. Remove un-needed outlets, bindings, and views from the nib. BUG=11916. TEST=loading pages, opening and closing tabs.
Review URL: http://codereview.chromium.org/115527 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@16410 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r--chrome/app/nibs/en.lproj/TabView.xib147
-rw-r--r--chrome/browser/cocoa/tab_cell.h7
-rw-r--r--chrome/browser/cocoa/tab_cell.mm13
-rw-r--r--chrome/browser/cocoa/tab_controller.h25
-rw-r--r--chrome/browser/cocoa/tab_controller.mm17
-rw-r--r--chrome/browser/cocoa/tab_strip_controller.mm42
-rw-r--r--chrome/browser/cocoa/throbber_view.h37
-rw-r--r--chrome/browser/cocoa/throbber_view.mm88
-rw-r--r--chrome/browser/cocoa/throbber_view_unittest.mm44
-rw-r--r--chrome/chrome.gyp5
10 files changed, 327 insertions, 98 deletions
diff --git a/chrome/app/nibs/en.lproj/TabView.xib b/chrome/app/nibs/en.lproj/TabView.xib
index 3cd6798..9752f4b 100644
--- a/chrome/app/nibs/en.lproj/TabView.xib
+++ b/chrome/app/nibs/en.lproj/TabView.xib
@@ -2,10 +2,10 @@
<archive type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="7.03">
<data>
<int key="IBDocument.SystemTarget">1050</int>
- <string key="IBDocument.SystemVersion">9G55</string>
+ <string key="IBDocument.SystemVersion">9F33</string>
<string key="IBDocument.InterfaceBuilderVersion">677</string>
- <string key="IBDocument.AppKitVersion">949.43</string>
- <string key="IBDocument.HIToolboxVersion">353.00</string>
+ <string key="IBDocument.AppKitVersion">949.34</string>
+ <string key="IBDocument.HIToolboxVersion">352.00</string>
<object class="NSMutableArray" key="IBDocument.EditedObjectIDs">
<bool key="EncodedWithXMLCoder">YES</bool>
<integer value="1"/>
@@ -58,10 +58,6 @@
<reference key="NSControlView" ref="279592484"/>
<int key="NSButtonFlags">-2034482945</int>
<int key="NSButtonFlags2">134</int>
- <object class="NSCustomResource" key="NSNormalImage">
- <string key="NSClassName">NSImage</string>
- <string key="NSResourceName">nav</string>
- </object>
<string key="NSAlternateContents"/>
<string key="NSKeyEquivalent"/>
<int key="NSPeriodicDelay">400</int>
@@ -100,15 +96,37 @@
</object>
<bool key="NSEditable">YES</bool>
</object>
- <object class="NSProgressIndicator" id="58797509">
+ <object class="NSImageView" id="987403091">
<reference key="NSNextResponder" ref="1005"/>
- <int key="NSvFlags">-2147482327</int>
- <object class="NSPSMatrix" key="NSDrawMatrix"/>
- <string key="NSFrame">{{131, 4}, {16, 16}}</string>
+ <int key="NSvFlags">268</int>
+ <object class="NSMutableSet" key="NSDragTypes">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <object class="NSMutableArray" key="set.sortedObjects">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <string>Apple PDF pasteboard type</string>
+ <string>Apple PICT pasteboard type</string>
+ <string>Apple PNG pasteboard type</string>
+ <string>NSFilenamesPboardType</string>
+ <string>NeXT Encapsulated PostScript v1.2 pasteboard type</string>
+ <string>NeXT TIFF v4.0 pasteboard type</string>
+ </object>
+ </object>
+ <string key="NSFrame">{{18, 5}, {16, 16}}</string>
<reference key="NSSuperview" ref="1005"/>
- <int key="NSpiFlags">28938</int>
- <double key="NSMinValue">1.600000e+01</double>
- <double key="NSMaxValue">1.000000e+02</double>
+ <bool key="NSEnabled">YES</bool>
+ <object class="NSImageCell" key="NSCell" id="1061639670">
+ <int key="NSCellFlags">537001472</int>
+ <int key="NSCellFlags2">33587200</int>
+ <object class="NSCustomResource" key="NSContents">
+ <string key="NSClassName">NSImage</string>
+ <string key="NSResourceName">nav</string>
+ </object>
+ <int key="NSAlign">0</int>
+ <int key="NSScale">2</int>
+ <int key="NSStyle">0</int>
+ <bool key="NSAnimates">NO</bool>
+ </object>
+ <bool key="NSEditable">YES</bool>
</object>
<object class="NSButton" id="1054640993">
<reference key="NSNextResponder" ref="1005"/>
@@ -160,14 +178,6 @@
<int key="connectionID">25</int>
</object>
<object class="IBConnectionRecord">
- <object class="IBOutletConnection" key="connection">
- <string key="label">progressIndicator_</string>
- <reference key="source" ref="1001"/>
- <reference key="destination" ref="58797509"/>
- </object>
- <int key="connectionID">27</int>
- </object>
- <object class="IBConnectionRecord">
<object class="IBBindingConnection" key="connection">
<string key="label">title: title</string>
<reference key="source" ref="707804163"/>
@@ -185,58 +195,6 @@
</object>
<object class="IBConnectionRecord">
<object class="IBBindingConnection" key="connection">
- <string key="label">animate: loading</string>
- <reference key="source" ref="58797509"/>
- <reference key="destination" ref="1001"/>
- <object class="NSNibBindingConnector" key="connector">
- <reference key="NSSource" ref="58797509"/>
- <reference key="NSDestination" ref="1001"/>
- <string key="NSLabel">animate: loading</string>
- <string key="NSBinding">animate</string>
- <string key="NSKeyPath">loading</string>
- <int key="NSNibBindingConnectorVersion">2</int>
- </object>
- </object>
- <int key="connectionID">31</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBBindingConnection" key="connection">
- <string key="label">hidden: loading</string>
- <reference key="source" ref="58797509"/>
- <reference key="destination" ref="1001"/>
- <object class="NSNibBindingConnector" key="connector">
- <reference key="NSSource" ref="58797509"/>
- <reference key="NSDestination" ref="1001"/>
- <string key="NSLabel">hidden: loading</string>
- <string key="NSBinding">hidden</string>
- <string key="NSKeyPath">loading</string>
- <object class="NSDictionary" key="NSOptions">
- <string key="NS.key.0">NSValueTransformerName</string>
- <string key="NS.object.0">NSNegateBoolean</string>
- </object>
- <int key="NSNibBindingConnectorVersion">2</int>
- </object>
- </object>
- <int key="connectionID">43</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBBindingConnection" key="connection">
- <string key="label">image: image</string>
- <reference key="source" ref="279592484"/>
- <reference key="destination" ref="1001"/>
- <object class="NSNibBindingConnector" key="connector" id="703997475">
- <reference key="NSSource" ref="279592484"/>
- <reference key="NSDestination" ref="1001"/>
- <string key="NSLabel">image: image</string>
- <string key="NSBinding">image</string>
- <string key="NSKeyPath">image</string>
- <int key="NSNibBindingConnectorVersion">2</int>
- </object>
- </object>
- <int key="connectionID">44</int>
- </object>
- <object class="IBConnectionRecord">
- <object class="IBBindingConnection" key="connection">
<string key="label">title: title</string>
<reference key="source" ref="279592484"/>
<reference key="destination" ref="1001"/>
@@ -246,7 +204,7 @@
<string key="NSLabel">title: title</string>
<string key="NSBinding">title</string>
<string key="NSKeyPath">title</string>
- <reference key="NSPreviousConnector" ref="703997475"/>
+ <reference key="NSPreviousConnector"/>
<int key="NSNibBindingConnectorVersion">2</int>
</object>
</object>
@@ -284,6 +242,14 @@
</object>
<int key="connectionID">54</int>
</object>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">iconView_</string>
+ <reference key="source" ref="1001"/>
+ <reference key="destination" ref="987403091"/>
+ </object>
+ <int key="connectionID">58</int>
+ </object>
</object>
<object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects">
@@ -320,9 +286,9 @@
<object class="NSMutableArray" key="children">
<bool key="EncodedWithXMLCoder">YES</bool>
<reference ref="569334633"/>
- <reference ref="58797509"/>
<reference ref="279592484"/>
<reference ref="1054640993"/>
+ <reference ref="987403091"/>
</object>
<reference key="parent" ref="1002"/>
</object>
@@ -341,11 +307,6 @@
<reference key="parent" ref="569334633"/>
</object>
<object class="IBObjectRecord">
- <int key="objectID">12</int>
- <reference key="object" ref="58797509"/>
- <reference key="parent" ref="1005"/>
- </object>
- <object class="IBObjectRecord">
<int key="objectID">17</int>
<reference key="object" ref="279592484"/>
<object class="NSMutableArray" key="children">
@@ -373,6 +334,20 @@
<reference key="object" ref="348599947"/>
<reference key="parent" ref="1054640993"/>
</object>
+ <object class="IBObjectRecord">
+ <int key="objectID">55</int>
+ <reference key="object" ref="987403091"/>
+ <object class="NSMutableArray" key="children">
+ <bool key="EncodedWithXMLCoder">YES</bool>
+ <reference ref="1061639670"/>
+ </object>
+ <reference key="parent" ref="1005"/>
+ </object>
+ <object class="IBObjectRecord">
+ <int key="objectID">56</int>
+ <reference key="object" ref="1061639670"/>
+ <reference key="parent" ref="987403091"/>
+ </object>
</object>
</object>
<object class="NSMutableDictionary" key="flattenedProperties">
@@ -387,13 +362,14 @@
<string>1.IBViewEditorWindowController.showingLayoutRectangles</string>
<string>1.WindowOrigin</string>
<string>1.editorWindowContentRectSynchronizationRect</string>
- <string>12.IBPluginDependency</string>
<string>17.IBEditorWindowLastContentRect</string>
<string>17.IBPluginDependency</string>
<string>18.CustomClassName</string>
<string>18.IBPluginDependency</string>
<string>50.IBPluginDependency</string>
<string>51.IBPluginDependency</string>
+ <string>55.IBPluginDependency</string>
+ <string>56.IBPluginDependency</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
@@ -405,13 +381,14 @@
<boolean value="NO"/>
<string>{628, 654}</string>
<string>{{217, 442}, {480, 272}}</string>
- <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>{{77, 725}, {134, 29}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>TabCell</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
+ <string>com.apple.InterfaceBuilder.CocoaPlugin</string>
</object>
</object>
<object class="NSMutableDictionary" key="unlocalizedProperties">
@@ -434,7 +411,7 @@
</object>
</object>
<nil key="sourceID"/>
- <int key="maxID">54</int>
+ <int key="maxID">58</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions">
@@ -466,12 +443,14 @@
<object class="NSMutableArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>backgroundButton_</string>
+ <string>iconView_</string>
<string>progressIndicator_</string>
<string>target_</string>
</object>
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>NSButton</string>
+ <string>NSView</string>
<string>NSProgressIndicator</string>
<string>id</string>
</object>
diff --git a/chrome/browser/cocoa/tab_cell.h b/chrome/browser/cocoa/tab_cell.h
index a3ad120..0f0ff0f 100644
--- a/chrome/browser/cocoa/tab_cell.h
+++ b/chrome/browser/cocoa/tab_cell.h
@@ -7,8 +7,11 @@
#import <Cocoa/Cocoa.h>
-// A button cell that handles drawing/highlighting of tabs in the
-// tab bar.
+// A button cell that handles drawing/highlighting of tabs in the tab bar. Text
+// drawing leaves room for an icon view on the left of the tab and a close
+// button on the right. Technically, though, it doesn't know anything about what
+// it's leaving space for, so they could be reversed or even replaced by views
+// for other purposes.
@interface TabCell : NSButtonCell {
}
diff --git a/chrome/browser/cocoa/tab_cell.mm b/chrome/browser/cocoa/tab_cell.mm
index 205c7a5..a2adf27 100644
--- a/chrome/browser/cocoa/tab_cell.mm
+++ b/chrome/browser/cocoa/tab_cell.mm
@@ -145,11 +145,14 @@ namespace {
[[NSGraphicsContext currentContext] restoreGraphicsState];
- // Inset where the text and favicon are drawn to keep them away from the
- // sloping edges of the tab and the close box.
- int kInteriorInset = cellFrame.size.height / 2.0;
- NSRect frame = NSInsetRect(cellFrame, kInteriorInset, 0);
- frame.size.width -= 16; // Inset for close box
+ // Inset where the text is drawn to keep it away from the sloping edges of the
+ // tab, the close box, and the icon view. These constants are derived
+ // empirically as the cell doesn't know about the surrounding view objects.
+ // TODO(pinkerton/alcor): Fix this somehow?
+ const int kIconXOffset = 28;
+ const int kCloseBoxXOffset = 16;
+ NSRect frame = NSOffsetRect(cellFrame, kIconXOffset, 0);
+ frame.size.width -= kCloseBoxXOffset + kIconXOffset;
[self drawInteriorWithFrame:frame
inView:controlView];
}
diff --git a/chrome/browser/cocoa/tab_controller.h b/chrome/browser/cocoa/tab_controller.h
index 45d93ab..d7783b7 100644
--- a/chrome/browser/cocoa/tab_controller.h
+++ b/chrome/browser/cocoa/tab_controller.h
@@ -14,21 +14,33 @@
// to be sent a message when the tab is selected by the user clicking. Setting
// the |loading| property to YES visually indicates that this tab is currently
// loading content via a spinner.
+//
+// The tab has the notion of an "icon view" which can be used to display
+// identifying characteristics such as a favicon, or since it's a full-fledged
+// view, something with state and animation such as a throbber for illustrating
+// progress. The default in the nib is an image view so nothing special is
+// required if that's all you need.
@interface TabController : NSViewController {
@private
IBOutlet NSButton *backgroundButton_;
- IBOutlet NSProgressIndicator *progressIndicator_;
+ IBOutlet NSView* iconView_;
BOOL selected_;
BOOL loading_;
- NSImage *image_;
+ BOOL waiting_;
id<TabControllerTarget> target_; // weak, where actions are sent
SEL action_; // selector sent when tab is selected by clicking
}
-@property(retain, nonatomic) NSImage *image;
-@property(assign, nonatomic) BOOL selected;
+// The loading/waiting state of the tab.
+// TODO(pinkerton): these really don't belong here, but something needs to
+// know the state and another parallel array in TabStripController doesn't seem
+// like the right place either. In a perfect world, this class shouldn't know
+// anything about states that are specific to a browser.
@property(assign, nonatomic) BOOL loading;
+@property(assign, nonatomic) BOOL waiting;
+
+@property(assign, nonatomic) BOOL selected;
@property(assign, nonatomic) id target;
@property(assign, nonatomic) SEL action;
@@ -43,6 +55,11 @@
// perform the close.
- (IBAction)closeTab:(id)sender;
+// Replace the current icon view with the given view. |iconView| will be
+// resized to the size of the current icon view.
+- (void)setIconView:(NSView*)iconView;
+- (NSView*)iconView;
+
@end
#endif // CHROME_BROWSER_COCOA_TAB_CONTROLLER_H_
diff --git a/chrome/browser/cocoa/tab_controller.mm b/chrome/browser/cocoa/tab_controller.mm
index 027f709..c5aae1a 100644
--- a/chrome/browser/cocoa/tab_controller.mm
+++ b/chrome/browser/cocoa/tab_controller.mm
@@ -8,8 +8,8 @@
@implementation TabController
-@synthesize image = image_;
@synthesize loading = loading_;
+@synthesize waiting = waiting_;
@synthesize target = target_;
@synthesize action = action_;
@@ -23,13 +23,11 @@
- (id)init {
self = [super initWithNibName:@"TabView" bundle:mac_util::MainAppBundle()];
if (self != nil) {
- [self setImage:[NSImage imageNamed:@"nav"]];
}
return self;
}
- (void)dealloc {
- [image_ release];
[super dealloc];
}
@@ -44,6 +42,7 @@
// Called when the tab's nib is done loading and all outlets are hooked up.
- (void)awakeFromNib {
+ [(id)iconView_ setImage:[NSImage imageNamed:@"nav"]];
[[self view] addSubview:backgroundButton_
positioned:NSWindowBelow
relativeTo:nil];
@@ -66,4 +65,16 @@
return selected_;
}
+- (void)setIconView:(NSView*)iconView {
+ NSRect currentFrame = [iconView_ frame];
+ [iconView_ removeFromSuperview];
+ iconView_ = iconView;
+ [iconView_ setFrame:currentFrame];
+ [[self view] addSubview:iconView_];
+}
+
+- (NSView*)iconView {
+ return iconView_;
+}
+
@end
diff --git a/chrome/browser/cocoa/tab_strip_controller.mm b/chrome/browser/cocoa/tab_strip_controller.mm
index bbc18f0..2bbdec8 100644
--- a/chrome/browser/cocoa/tab_strip_controller.mm
+++ b/chrome/browser/cocoa/tab_strip_controller.mm
@@ -16,6 +16,7 @@
#import "chrome/browser/cocoa/tab_controller.h"
#import "chrome/browser/cocoa/tab_strip_model_observer_bridge.h"
#import "chrome/browser/cocoa/tab_view.h"
+#import "chrome/browser/cocoa/throbber_view.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/browser/tab_contents/tab_contents_view.h"
#include "chrome/browser/tabs/tab_strip_model.h"
@@ -357,6 +358,18 @@
[self layoutTabs];
}
+// A helper routine for creating an NSImageView to hold the fav icon for
+// |contents|.
+// TODO(pinkerton): fill in with code to use the real favicon, not the default
+// for all cases.
+- (NSImageView*)favIconImageViewForContents:(TabContents*)contents {
+ NSRect iconFrame = NSMakeRect(0, 0, 16, 16);
+ NSImageView* view = [[[NSImageView alloc] initWithFrame:iconFrame]
+ autorelease];
+ [view setImage:[NSImage imageNamed:@"nav"]];
+ return view;
+}
+
// Called when a notification is received from the model that the given tab
// has been updated. |loading| will be YES when we only want to update the
// throbber state, not anything else about the (partially) loading tab.
@@ -366,6 +379,35 @@
if (!loading)
[self setTabTitle:[tabArray_ objectAtIndex:index] withContents:contents];
+ // Update the current loading state, replacing the icon with a throbber, or
+ // vice versa. This will get called repeatedly with the same state during a
+ // load, so we need to make sure we're not creating the throbber view over and
+ // over.
+ if (contents) {
+ TabController* tabController = [tabArray_ objectAtIndex:index];
+ NSString* imageName = nil;
+ if (contents->waiting_for_response() && ![tabController waiting]) {
+ imageName = @"throbber_waiting";
+ [tabController setWaiting:YES];
+ } else if (contents->is_loading() && ![tabController loading]) {
+ imageName = @"throbber";
+ [tabController setLoading:YES];
+ }
+ if (imageName) {
+ NSRect frame = NSMakeRect(0, 0, 16, 16);
+ NSImage* image = [NSImage imageNamed:imageName];
+ ThrobberView* throbber =
+ [[[ThrobberView alloc] initWithFrame:frame image:image] autorelease];
+ [tabController setIconView:throbber];
+ }
+ else if (!contents->is_loading()) {
+ // Set everything back to normal, we're done loading.
+ [tabController setIconView:[self favIconImageViewForContents:contents]];
+ [tabController setWaiting:NO];
+ [tabController setLoading:NO];
+ }
+ }
+
TabContentsController* updatedController =
[tabContentsArray_ objectAtIndex:index];
[updatedController tabDidChange:contents];
diff --git a/chrome/browser/cocoa/throbber_view.h b/chrome/browser/cocoa/throbber_view.h
new file mode 100644
index 0000000..5403371
--- /dev/null
+++ b/chrome/browser/cocoa/throbber_view.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef CHROME_BROWSER_COCOA_THROBBER_VIEW_H_
+#define CHROME_BROWSER_COCOA_THROBBER_VIEW_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/scoped_nsobject.h"
+
+@class TimerTarget;
+
+// A class that knows how to draw an animated state to indicate progress.
+// Currently draws via a sequence of frames in an image, but may ultimately be
+// written to render paths that are rotated around a center origin. Creating
+// the class starts the animation, destroying it stops it. There is no state
+// where the class is frozen on an image and not animating.
+
+@interface ThrobberView : NSView {
+ @private
+ scoped_nsobject<NSImage> image_;
+ scoped_nsobject<TimerTarget> target_; // Target of animation timer.
+ NSTimer* timer_; // Animation timer. Weak, owned by runloop.
+ unsigned int numFrames_; // Number of frames in this animation.
+ unsigned int animationFrame_; // Current frame of the animation,
+ // [0..numFrames_)
+}
+
+// Creates the view with |frame| and the image strip desginated by |image|. The
+// image needs to be made of squares such that the height divides evently into
+// the width. Takes ownership of |image|.
+- (id)initWithFrame:(NSRect)frame image:(NSImage*)image;
+
+@end
+
+#endif // CHROME_BROWSER_COCOA_THROBBER_VIEW_H_
diff --git a/chrome/browser/cocoa/throbber_view.mm b/chrome/browser/cocoa/throbber_view.mm
new file mode 100644
index 0000000..948dcb4
--- /dev/null
+++ b/chrome/browser/cocoa/throbber_view.mm
@@ -0,0 +1,88 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "chrome/browser/cocoa/throbber_view.h"
+
+#include "base/logging.h"
+
+const float kAnimationIntervalSeconds = 0.03; // 30ms, same as windows
+
+@interface ThrobberView(PrivateMethods)
+- (void)animate;
+@end
+
+// A very simple object that is the target for the animation timer so that
+// the view isn't. We do this to avoid retain cycles as the timer
+// retains its target.
+@interface TimerTarget : NSObject {
+ @private
+ ThrobberView* throbber_; // Weak, owns us
+}
+- (id)initWithThrobber:(ThrobberView*)view;
+@end
+
+@implementation TimerTarget
+- (id)initWithThrobber:(ThrobberView*)view {
+ if ((self = [super init])) {
+ throbber_ = view;
+ }
+ return self;
+}
+
+- (void)animate:(NSTimer*)timer {
+ [throbber_ animate];
+}
+@end
+
+@implementation ThrobberView
+
+- (id)initWithFrame:(NSRect)frame image:(NSImage*)image {
+ if ((self = [super initWithFrame:frame])) {
+ // Ensure that the height divides evenly into the width. Cache the
+ // number of frames in the animation for later.
+ NSSize imageSize = [image size];
+ DCHECK(imageSize.height && imageSize.width);
+ if (!imageSize.height)
+ return nil;
+ DCHECK((int)imageSize.width % (int)imageSize.height == 0);
+ numFrames_ = (int)imageSize.width / (int)imageSize.height;
+ DCHECK(numFrames_);
+ image_.reset([image retain]);
+
+ // Start a timer for the animation frames.
+ target_.reset([[TimerTarget alloc] initWithThrobber:self]);
+ timer_ =
+ [NSTimer scheduledTimerWithTimeInterval:kAnimationIntervalSeconds
+ target:target_.get()
+ selector:@selector(animate:)
+ userInfo:nil
+ repeats:YES];
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [timer_ invalidate];
+ [super dealloc];
+}
+
+// Called when the TimerTarget gets tickled by our timer. Increment the frame
+// counter and mark as needing display.
+- (void)animate {
+ animationFrame_ = ++animationFrame_ % numFrames_;
+ [self setNeedsDisplay:YES];
+}
+
+// Overridden to draw the appropriate frame in the image strip.
+- (void)drawRect:(NSRect)rect {
+ float imageDimension = [image_ size].height;
+ float xOffset = animationFrame_ * imageDimension;
+ NSRect sourceImageRect =
+ NSMakeRect(xOffset, 0, imageDimension, imageDimension);
+ [image_ compositeToPoint:NSMakePoint(0, 0)
+ fromRect:sourceImageRect
+ operation:NSCompositeSourceOver];
+}
+
+@end
diff --git a/chrome/browser/cocoa/throbber_view_unittest.mm b/chrome/browser/cocoa/throbber_view_unittest.mm
new file mode 100644
index 0000000..72d2240
--- /dev/null
+++ b/chrome/browser/cocoa/throbber_view_unittest.mm
@@ -0,0 +1,44 @@
+// Copyright (c) 2009 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/scoped_nsobject.h"
+#import "chrome/browser/cocoa/throbber_view.h"
+#import "chrome/browser/cocoa/cocoa_test_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+namespace {
+
+class ThrobberViewTest : public PlatformTest {
+ public:
+ ThrobberViewTest() {
+ NSRect frame = NSMakeRect(10, 10, 16, 16);
+ NSBundle* bundle = mac_util::MainAppBundle();
+ NSImage* image = [[[NSImage alloc] initByReferencingFile:
+ [bundle pathForResource:@"throbber" ofType:@"png"]]
+ autorelease];
+ view_.reset([[ThrobberView alloc] initWithFrame:frame image:image]);
+ [cocoa_helper_.contentView() addSubview:view_.get()];
+ }
+
+ scoped_nsobject<ThrobberView> view_;
+ CocoaTestHelper cocoa_helper_; // Inits Cocoa, creates window, etc...
+};
+
+// Test adding/removing from the view hierarchy, mostly to ensure nothing
+// leaks or crashes.
+TEST_F(ThrobberViewTest, AddRemove) {
+ EXPECT_EQ(cocoa_helper_.contentView(), [view_ superview]);
+ [view_.get() removeFromSuperview];
+ EXPECT_FALSE([view_ superview]);
+}
+
+// Test drawing, mostly to ensure nothing leaks or crashes.
+TEST_F(ThrobberViewTest, Display) {
+ [view_ display];
+}
+
+} // namespace
diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp
index 83d5766..6155d2f 100644
--- a/chrome/chrome.gyp
+++ b/chrome/chrome.gyp
@@ -671,6 +671,8 @@
'browser/cocoa/tab_view.mm',
'browser/cocoa/tab_window_controller.h',
'browser/cocoa/tab_window_controller.mm',
+ 'browser/cocoa/throbber_view.h',
+ 'browser/cocoa/throbber_view.mm',
'browser/cocoa/toolbar_button_cell.h',
'browser/cocoa/toolbar_button_cell.mm',
'browser/cocoa/toolbar_controller.h',
@@ -1933,6 +1935,8 @@
'app/theme/star.pdf',
'app/theme/starred.pdf',
'app/theme/stop.pdf',
+ 'app/theme/throbber.png',
+ 'app/theme/throbber_waiting.png',
'app/app-Info.plist',
],
# TODO(mark): Come up with a fancier way to do this. It should only
@@ -2711,6 +2715,7 @@
'browser/cocoa/tab_strip_controller_unittest.mm',
'browser/cocoa/tab_strip_view_unittest.mm',
'browser/cocoa/tab_view_unittest.mm',
+ 'browser/cocoa/throbber_view_unittest.mm',
'browser/cocoa/toolbar_button_cell_unittest.mm',
'browser/cocoa/toolbar_controller_unittest.mm',
'browser/cocoa/toolbar_view_unittest.mm',