diff options
author | pinkerton@chromium.org <pinkerton@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-19 20:46:37 +0000 |
---|---|---|
committer | pinkerton@chromium.org <pinkerton@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-19 20:46:37 +0000 |
commit | 1ae99501e0a15b85f0593a71c939063027c802d4 (patch) | |
tree | ae2c255da0ff4687c78b2a017dd1b660a8ed343c /chrome/browser | |
parent | f8fc92385be0fb12cfffd14db9c6f3b461bec447 (diff) | |
download | chromium_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/browser')
-rw-r--r-- | chrome/browser/cocoa/tab_cell.h | 7 | ||||
-rw-r--r-- | chrome/browser/cocoa/tab_cell.mm | 13 | ||||
-rw-r--r-- | chrome/browser/cocoa/tab_controller.h | 25 | ||||
-rw-r--r-- | chrome/browser/cocoa/tab_controller.mm | 17 | ||||
-rw-r--r-- | chrome/browser/cocoa/tab_strip_controller.mm | 42 | ||||
-rw-r--r-- | chrome/browser/cocoa/throbber_view.h | 37 | ||||
-rw-r--r-- | chrome/browser/cocoa/throbber_view.mm | 88 | ||||
-rw-r--r-- | chrome/browser/cocoa/throbber_view_unittest.mm | 44 |
8 files changed, 259 insertions, 14 deletions
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 |