summaryrefslogtreecommitdiffstats
path: root/chrome/browser
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/browser
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/browser')
-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
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