summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsail@chromium.org <sail@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-11 05:52:50 +0000
committersail@chromium.org <sail@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-11 05:52:50 +0000
commitd903ef8a4be2bc9ee4b1fb71f6c5b78983f8b8d6 (patch)
treeb13d59e26092548d3f0f3940ce5a1aade67fb290
parentbfcade631b388e9718d7f48a571eff03d931304e (diff)
downloadchromium_src-d903ef8a4be2bc9ee4b1fb71f6c5b78983f8b8d6.zip
chromium_src-d903ef8a4be2bc9ee4b1fb71f6c5b78983f8b8d6.tar.gz
chromium_src-d903ef8a4be2bc9ee4b1fb71f6c5b78983f8b8d6.tar.bz2
Mac Web Intents Part 11: Progress view
This CL adds a progress view to the web intents picker dialog. The view is used to display the "waiting for chrome web store" and "installing extension" progress. Screenshot: http://i.imgur.com/riMRo.png BUG=152010 Review URL: https://chromiumcodereview.appspot.com/11009017 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@161302 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/download/download_util.cc98
-rw-r--r--chrome/browser/download/download_util.h10
-rw-r--r--chrome/browser/ui/cocoa/intents/web_intent_picker_view_controller.h6
-rw-r--r--chrome/browser/ui/cocoa/intents/web_intent_picker_view_controller.mm48
-rw-r--r--chrome/browser/ui/cocoa/intents/web_intent_picker_view_controller_browsertest.mm38
-rw-r--r--chrome/browser/ui/cocoa/intents/web_intent_progress_view_controller.h33
-rw-r--r--chrome/browser/ui/cocoa/intents/web_intent_progress_view_controller.mm135
-rw-r--r--chrome/browser/ui/cocoa/intents/web_intent_progress_view_controller_unittest.mm63
-rw-r--r--chrome/browser/ui/cocoa/spinner_progress_indicator.h35
-rw-r--r--chrome/browser/ui/cocoa/spinner_progress_indicator.mm113
-rw-r--r--chrome/browser/ui/cocoa/spinner_progress_indicator_unittest.mm41
-rw-r--r--chrome/chrome_browser_ui.gypi4
-rw-r--r--chrome/chrome_tests.gypi2
13 files changed, 585 insertions, 41 deletions
diff --git a/chrome/browser/download/download_util.cc b/chrome/browser/download/download_util.cc
index 3c8b59d..7a01702 100644
--- a/chrome/browser/download/download_util.cc
+++ b/chrome/browser/download/download_util.cc
@@ -171,6 +171,61 @@ gfx::ImageSkia* g_background_16 = NULL;
gfx::ImageSkia* g_foreground_32 = NULL;
gfx::ImageSkia* g_background_32 = NULL;
+void PaintCustomDownloadProgress(gfx::Canvas* canvas,
+ const gfx::ImageSkia& background_image,
+ const gfx::ImageSkia& foreground_image,
+ int image_size,
+ const gfx::Rect& bounds,
+ int start_angle,
+ int percent_done) {
+ // Draw the background progress image.
+ canvas->DrawImageInt(background_image,
+ bounds.x(),
+ bounds.y());
+
+ // Layer the foreground progress image in an arc proportional to the download
+ // progress. The arc grows clockwise, starting in the midnight position, as
+ // the download progresses. However, if the download does not have known total
+ // size (the server didn't give us one), then we just spin an arc around until
+ // we're done.
+ float sweep_angle = 0.0;
+ float start_pos = static_cast<float>(kStartAngleDegrees);
+ if (percent_done < 0) {
+ sweep_angle = kUnknownAngleDegrees;
+ start_pos = static_cast<float>(start_angle);
+ } else if (percent_done > 0) {
+ sweep_angle = static_cast<float>(kMaxDegrees / 100.0 * percent_done);
+ }
+
+ // Set up an arc clipping region for the foreground image. Don't bother using
+ // a clipping region if it would round to 360 (really 0) degrees, since that
+ // would eliminate the foreground completely and be quite confusing (it would
+ // look like 0% complete when it should be almost 100%).
+ canvas->Save();
+ if (sweep_angle < static_cast<float>(kMaxDegrees - 1)) {
+ SkRect oval;
+ oval.set(SkIntToScalar(bounds.x()),
+ SkIntToScalar(bounds.y()),
+ SkIntToScalar(bounds.x() + image_size),
+ SkIntToScalar(bounds.y() + image_size));
+ SkPath path;
+ path.arcTo(oval,
+ SkFloatToScalar(start_pos),
+ SkFloatToScalar(sweep_angle), false);
+ path.lineTo(SkIntToScalar(bounds.x() + image_size / 2),
+ SkIntToScalar(bounds.y() + image_size / 2));
+
+ // gfx::Canvas::ClipPath does not provide for anti-aliasing.
+ canvas->sk_canvas()->clipPath(path, SkRegion::kIntersect_Op, true);
+ }
+
+ canvas->DrawImageInt(foreground_image,
+ bounds.x(),
+ bounds.y());
+ canvas->Restore();
+}
+
+
void PaintDownloadProgress(gfx::Canvas* canvas,
#if defined(TOOLKIT_VIEWS)
views::View* containing_view,
@@ -217,46 +272,9 @@ void PaintDownloadProgress(gfx::Canvas* canvas,
bounds.x(),
bounds.y());
- // Layer the foreground progress image in an arc proportional to the download
- // progress. The arc grows clockwise, starting in the midnight position, as
- // the download progresses. However, if the download does not have known total
- // size (the server didn't give us one), then we just spin an arc around until
- // we're done.
- float sweep_angle = 0.0;
- float start_pos = static_cast<float>(kStartAngleDegrees);
- if (percent_done < 0) {
- sweep_angle = kUnknownAngleDegrees;
- start_pos = static_cast<float>(start_angle);
- } else if (percent_done > 0) {
- sweep_angle = static_cast<float>(kMaxDegrees / 100.0 * percent_done);
- }
-
- // Set up an arc clipping region for the foreground image. Don't bother using
- // a clipping region if it would round to 360 (really 0) degrees, since that
- // would eliminate the foreground completely and be quite confusing (it would
- // look like 0% complete when it should be almost 100%).
- canvas->Save();
- if (sweep_angle < static_cast<float>(kMaxDegrees - 1)) {
- SkRect oval;
- oval.set(SkIntToScalar(bounds.x()),
- SkIntToScalar(bounds.y()),
- SkIntToScalar(bounds.x() + kProgressIconSize),
- SkIntToScalar(bounds.y() + kProgressIconSize));
- SkPath path;
- path.arcTo(oval,
- SkFloatToScalar(start_pos),
- SkFloatToScalar(sweep_angle), false);
- path.lineTo(SkIntToScalar(bounds.x() + kProgressIconSize / 2),
- SkIntToScalar(bounds.y() + kProgressIconSize / 2));
-
- // gfx::Canvas::ClipPath does not provide for anti-aliasing.
- canvas->sk_canvas()->clipPath(path, SkRegion::kIntersect_Op, true);
- }
-
- canvas->DrawImageInt(*foreground,
- bounds.x(),
- bounds.y());
- canvas->Restore();
+ PaintCustomDownloadProgress(canvas, *background, *foreground,
+ kProgressIconSize, bounds, start_angle,
+ percent_done);
}
void PaintDownloadComplete(gfx::Canvas* canvas,
diff --git a/chrome/browser/download/download_util.h b/chrome/browser/download/download_util.h
index 6d98314..183626c 100644
--- a/chrome/browser/download/download_util.h
+++ b/chrome/browser/download/download_util.h
@@ -32,6 +32,8 @@ class DownloadItem;
namespace gfx {
class Canvas;
class Image;
+class ImageSkia;
+class Rect;
}
namespace download_util {
@@ -91,6 +93,14 @@ enum PaintDownloadProgressSize {
// require the containing View in addition to the canvas because if we are
// drawing in a right-to-left locale, we need to mirror the position of the
// progress animation within the containing View.
+void PaintCustomDownloadProgress(gfx::Canvas* canvas,
+ const gfx::ImageSkia& background_image,
+ const gfx::ImageSkia& foreground_image,
+ int image_size,
+ const gfx::Rect& bounds,
+ int start_angle,
+ int percent_done);
+
void PaintDownloadProgress(gfx::Canvas* canvas,
#if defined(TOOLKIT_VIEWS)
views::View* containing_view,
diff --git a/chrome/browser/ui/cocoa/intents/web_intent_picker_view_controller.h b/chrome/browser/ui/cocoa/intents/web_intent_picker_view_controller.h
index 99acdd1..5b97c2d 100644
--- a/chrome/browser/ui/cocoa/intents/web_intent_picker_view_controller.h
+++ b/chrome/browser/ui/cocoa/intents/web_intent_picker_view_controller.h
@@ -12,10 +12,13 @@
class WebIntentPickerCocoa2;
@class WebIntentMessageViewController;
+@class WebIntentProgressViewController;
// The different states a picker dialog can be in.
enum WebIntentPickerState {
+ PICKER_STATE_WAITING,
PICKER_STATE_NO_SERVICE,
+ PICKER_STATE_INSTALLING_EXTENSION,
};
// Manages the web intent picker UI. The view is meant to be embedded in either
@@ -27,6 +30,8 @@ enum WebIntentPickerState {
scoped_nsobject<NSButton> closeButton_;
scoped_nsobject<WebIntentMessageViewController>
messageViewController_;
+ scoped_nsobject<WebIntentProgressViewController>
+ progressViewController_;
}
- (id)initWithPicker:(WebIntentPickerCocoa2*)picker;
@@ -37,6 +42,7 @@ enum WebIntentPickerState {
- (WebIntentPickerState)state;
- (WebIntentMessageViewController*)messageViewController;
+- (WebIntentProgressViewController*)progressViewController;
// Update the dialog state and perform layout.
- (void)update;
diff --git a/chrome/browser/ui/cocoa/intents/web_intent_picker_view_controller.mm b/chrome/browser/ui/cocoa/intents/web_intent_picker_view_controller.mm
index 5183b2d..c52e0a9 100644
--- a/chrome/browser/ui/cocoa/intents/web_intent_picker_view_controller.mm
+++ b/chrome/browser/ui/cocoa/intents/web_intent_picker_view_controller.mm
@@ -4,10 +4,12 @@
#import "chrome/browser/ui/cocoa/intents/web_intent_picker_view_controller.h"
+#include "base/sys_string_conversions.h"
#import "chrome/browser/ui/cocoa/flipped_view.h"
#import "chrome/browser/ui/cocoa/hover_close_button.h"
#import "chrome/browser/ui/cocoa/intents/web_intent_message_view_controller.h"
#import "chrome/browser/ui/cocoa/intents/web_intent_picker_cocoa2.h"
+#import "chrome/browser/ui/cocoa/intents/web_intent_progress_view_controller.h"
#import "chrome/browser/ui/cocoa/key_equivalent_constants.h"
#include "chrome/browser/ui/constrained_window.h"
#include "chrome/browser/ui/intents/web_intent_picker_delegate.h"
@@ -26,7 +28,9 @@
- (WebIntentPickerState)newPickerState;
// Update the various views to match changes to the picker model.
+- (void)updateWaiting;
- (void)updateNoService;
+- (void)updateInstallingExtension;
- (void)onCloseButton:(id)sender;
- (void)cancelOperation:(id)sender;
@@ -51,7 +55,8 @@
messageViewController_.reset(
[[WebIntentMessageViewController alloc] init]);
-
+ progressViewController_.reset(
+ [[WebIntentProgressViewController alloc] init]);
}
return self;
}
@@ -68,6 +73,10 @@
return messageViewController_;
}
+- (WebIntentProgressViewController*)progressViewController {
+ return progressViewController_;
+}
+
- (void)update {
WebIntentPickerState newState = [self newPickerState];
NSView* currentView = [[self currentViewController] view];
@@ -83,9 +92,15 @@
}
switch (state_) {
+ case PICKER_STATE_WAITING:
+ [self updateWaiting];
+ break;
case PICKER_STATE_NO_SERVICE:
[self updateNoService];
break;
+ case PICKER_STATE_INSTALLING_EXTENSION:
+ [self updateInstallingExtension];
+ break;
}
[self performLayout];
@@ -130,16 +145,32 @@
- (NSViewController<WebIntentViewController>*)currentViewController {
switch (state_) {
+ case PICKER_STATE_WAITING:
+ return progressViewController_;
case PICKER_STATE_NO_SERVICE:
return messageViewController_;
+ case PICKER_STATE_INSTALLING_EXTENSION:
+ return progressViewController_;
}
return nil;
}
- (WebIntentPickerState)newPickerState {
+ WebIntentPickerModel* model = picker_->model();
+ if (!model->pending_extension_install_id().empty())
+ return PICKER_STATE_INSTALLING_EXTENSION;
+ if (model->IsWaitingForSuggestions())
+ return PICKER_STATE_WAITING;
return PICKER_STATE_NO_SERVICE;
}
+- (void)updateWaiting {
+ NSString* message = l10n_util::GetNSStringWithFixup(
+ IDS_INTENT_PICKER_WAIT_FOR_CWS);
+ [progressViewController_ setMessage:message];
+ [progressViewController_ setPercentDone:-1];
+}
+
- (void)updateNoService {
[messageViewController_ setTitle:l10n_util::GetNSStringWithFixup(
IDS_INTENT_PICKER_NO_SERVICES_TITLE)];
@@ -147,6 +178,21 @@
IDS_INTENT_PICKER_NO_SERVICES)];
}
+- (void)updateInstallingExtension {
+ WebIntentPickerModel* model = picker_->model();
+ const WebIntentPickerModel::SuggestedExtension* extension =
+ model->GetSuggestedExtensionWithId(
+ model->pending_extension_install_id());
+ if (!extension)
+ return;
+ [progressViewController_ setTitle:
+ base::SysUTF16ToNSString(extension->title)];
+ [progressViewController_ setMessage:base::SysUTF16ToNSString(
+ model->pending_extension_install_status_string())];
+ [progressViewController_ setPercentDone:
+ model->pending_extension_install_download_progress()];
+}
+
- (void)onCloseButton:(id)sender {
picker_->delegate()->OnUserCancelledPickerDialog();
}
diff --git a/chrome/browser/ui/cocoa/intents/web_intent_picker_view_controller_browsertest.mm b/chrome/browser/ui/cocoa/intents/web_intent_picker_view_controller_browsertest.mm
index 640b428..5aec6fb 100644
--- a/chrome/browser/ui/cocoa/intents/web_intent_picker_view_controller_browsertest.mm
+++ b/chrome/browser/ui/cocoa/intents/web_intent_picker_view_controller_browsertest.mm
@@ -8,8 +8,10 @@
#import "chrome/browser/ui/cocoa/intents/web_intent_message_view_controller.h"
#import "chrome/browser/ui/cocoa/intents/web_intent_picker_cocoa2.h"
#import "chrome/browser/ui/cocoa/intents/web_intent_picker_view_controller.h"
+#import "chrome/browser/ui/cocoa/intents/web_intent_progress_view_controller.h"
#import "chrome/browser/ui/cocoa/key_equivalent_constants.h"
#include "chrome/browser/ui/cocoa/run_loop_testing.h"
+#import "chrome/browser/ui/cocoa/spinner_progress_indicator.h"
#include "chrome/browser/ui/intents/web_intent_picker_delegate_mock.h"
#include "chrome/browser/ui/tab_contents/tab_contents.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -69,6 +71,16 @@ IN_PROC_BROWSER_TEST_F(WebIntentPickerViewControllerTest, CloseButton) {
[[controller_ closeButton] performClick:nil];
}
+// Test the "waiting for chrome web store" state.
+IN_PROC_BROWSER_TEST_F(WebIntentPickerViewControllerTest, Waiting) {
+ model_.SetWaitingForSuggestions(true);
+ EXPECT_EQ(PICKER_STATE_WAITING, [controller_ state]);
+ WebIntentProgressViewController* progress_controller =
+ [controller_ progressViewController];
+ EXPECT_NSEQ([controller_ view], [[progress_controller view] superview]);
+ EXPECT_TRUE([[progress_controller progressIndicator] isIndeterminate]);
+}
+
// Test the "no matching services" state.
IN_PROC_BROWSER_TEST_F(WebIntentPickerViewControllerTest, NoServices) {
model_.SetWaitingForSuggestions(false);
@@ -77,3 +89,29 @@ IN_PROC_BROWSER_TEST_F(WebIntentPickerViewControllerTest, NoServices) {
[controller_ messageViewController];
EXPECT_NSEQ([controller_ view], [[message_controller view] superview]);
}
+
+// Test the "installing a service" state.
+IN_PROC_BROWSER_TEST_F(WebIntentPickerViewControllerTest, Installing) {
+ // Add a suggested service.
+ std::vector<WebIntentPickerModel::SuggestedExtension> suggestions;
+ WebIntentPickerModel::SuggestedExtension suggestion(
+ ASCIIToUTF16("Title"), "1234", 2);
+ suggestions.push_back(suggestion);
+ model_.AddSuggestedExtensions(suggestions);
+
+ // Set a pending extension download.
+ model_.SetWaitingForSuggestions(false);
+ model_.SetPendingExtensionInstallId(suggestion.id);
+ EXPECT_EQ(PICKER_STATE_INSTALLING_EXTENSION, [controller_ state]);
+
+ WebIntentProgressViewController* progress_controller =
+ [controller_ progressViewController];
+ EXPECT_NSEQ([controller_ view], [[progress_controller view] superview]);
+ SpinnerProgressIndicator* progress_indicator =
+ [progress_controller progressIndicator];
+ EXPECT_FALSE([progress_indicator isIndeterminate]);
+
+ int percent_done = 50;
+ model_.SetPendingExtensionInstallDownloadProgress(percent_done);
+ EXPECT_EQ(percent_done, [progress_indicator percentDone]);
+}
diff --git a/chrome/browser/ui/cocoa/intents/web_intent_progress_view_controller.h b/chrome/browser/ui/cocoa/intents/web_intent_progress_view_controller.h
new file mode 100644
index 0000000..ede8f41
--- /dev/null
+++ b/chrome/browser/ui/cocoa/intents/web_intent_progress_view_controller.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 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_UI_COCOA_INTENTS_WEB_INTENT_PROGRESS_VIEW_CONTROLLER_H_
+#define CHROME_BROWSER_UI_COCOA_INTENTS_WEB_INTENT_PROGRESS_VIEW_CONTROLLER_H_
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/memory/scoped_nsobject.h"
+#import "chrome/browser/ui/cocoa/intents/web_intent_view_controller.h"
+
+@class SpinnerProgressIndicator;
+
+// The progress view shows a progress indicator and a label underneath it. The
+// label is made by joining the title and the message.
+@interface WebIntentProgressViewController : NSViewController
+ <WebIntentViewController> {
+ @private
+ scoped_nsobject<NSString> title_;
+ scoped_nsobject<NSString> message_;
+ scoped_nsobject<NSTextField> messageTextField_;
+ scoped_nsobject<SpinnerProgressIndicator> progressIndicator_;
+}
+
+- (SpinnerProgressIndicator*)progressIndicator;
+- (void)setTitle:(NSString*)title;
+- (void)setMessage:(NSString*)message;
+- (void)setPercentDone:(int)percent;
+
+@end
+
+#endif // CHROME_BROWSER_UI_COCOA_INTENTS_WEB_INTENT_PROGRESS_VIEW_CONTROLLER_H_
diff --git a/chrome/browser/ui/cocoa/intents/web_intent_progress_view_controller.mm b/chrome/browser/ui/cocoa/intents/web_intent_progress_view_controller.mm
new file mode 100644
index 0000000..73bc2cf
--- /dev/null
+++ b/chrome/browser/ui/cocoa/intents/web_intent_progress_view_controller.mm
@@ -0,0 +1,135 @@
+// Copyright (c) 2012 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/ui/cocoa/intents/web_intent_progress_view_controller.h"
+
+#include "base/memory/scoped_nsobject.h"
+#import "chrome/browser/ui/cocoa/constrained_window/constrained_window_control_utils.h"
+#import "chrome/browser/ui/cocoa/flipped_view.h"
+#import "chrome/browser/ui/cocoa/spinner_progress_indicator.h"
+#import "chrome/browser/ui/constrained_window.h"
+#include "grit/theme_resources.h"
+#include "third_party/GTM/AppKit/GTMUILocalizerAndLayoutTweaker.h"
+#include "ui/base/resource/resource_bundle.h"
+
+namespace {
+
+// Vertical space between the progress indicator and the message text field.
+const CGFloat kProgressMessageFieldSpacing = 15.0;
+
+// Joins the two strings with a space between them.
+NSAttributedString* JoinString(NSAttributedString* string1,
+ NSAttributedString* string2) {
+ if (![string1 length])
+ return string2;
+ if (![string2 length])
+ return string1;
+
+ NSMutableAttributedString* result =
+ [[[NSMutableAttributedString alloc] init] autorelease];
+ [result appendAttributedString:string1];
+ scoped_nsobject<NSAttributedString> space(
+ [[NSAttributedString alloc] initWithString:@" "]);
+ [result appendAttributedString:space];
+ [result appendAttributedString:string2];
+ return result;
+}
+
+} // namespace
+
+@interface WebIntentProgressViewController ()
+// Updates the message text field and resizes it to fit the given width.
+- (void)updateTextFieldAndResizeToWidth:(CGFloat)width;
+@end
+
+@implementation WebIntentProgressViewController
+
+- (id)init {
+ if ((self = [super init])) {
+ scoped_nsobject<NSView> view(
+ [[FlippedView alloc] initWithFrame:NSZeroRect]);
+ [self setView:view];
+
+ messageTextField_.reset([constrained_window::CreateLabel() retain]);
+ [[self view] addSubview:messageTextField_];
+
+ progressIndicator_.reset(
+ [[SpinnerProgressIndicator alloc] initWithFrame:NSZeroRect]);
+ [progressIndicator_ sizeToFit];
+ [[self view] addSubview:progressIndicator_];
+ }
+ return self;
+}
+
+- (SpinnerProgressIndicator*)progressIndicator {
+ return progressIndicator_;
+}
+
+- (void)setTitle:(NSString*)title {
+ title_.reset([title retain]);
+}
+
+- (void)setMessage:(NSString*)message {
+ message_.reset([message retain]);
+}
+
+- (void)setPercentDone:(int)percent {
+ if (percent == -1) {
+ [progressIndicator_ setIsIndeterminate:YES];
+ } else {
+ [progressIndicator_ setIsIndeterminate:NO];
+ [progressIndicator_ setPercentDone:percent];
+ }
+}
+
+- (NSSize)minimumSizeForInnerWidth:(CGFloat)innerWidth {
+ NSSize progressSize = [progressIndicator_ frame].size;
+ CGFloat width = std::max(innerWidth, progressSize.width);
+ [self updateTextFieldAndResizeToWidth:width];
+ CGFloat height = progressSize.height + NSHeight([messageTextField_ frame]) +
+ kProgressMessageFieldSpacing;
+ return NSMakeSize(width, height);
+}
+
+- (void)layoutSubviewsWithinFrame:(NSRect)innerFrame {
+ [self updateTextFieldAndResizeToWidth:NSWidth(innerFrame)];
+
+ NSRect progressFrame = [progressIndicator_ frame];
+ progressFrame.origin.x = NSMidX(innerFrame) - NSWidth(progressFrame) / 2.0;
+ progressFrame.origin.y = NSMinY(innerFrame) +
+ NSHeight(innerFrame) / 3.0 - NSHeight(progressFrame) / 2.0;
+
+ NSRect textFrame = [messageTextField_ frame];
+ CGFloat newHeight = NSMaxY(progressFrame) + NSHeight(textFrame) +
+ kProgressMessageFieldSpacing;
+ if (newHeight > NSHeight(innerFrame))
+ progressFrame.origin.y = NSMinY(innerFrame);
+ [progressIndicator_ setFrame:progressFrame];
+
+ textFrame.origin.x = NSMinX(innerFrame);
+ textFrame.origin.y = NSMaxY(progressFrame) + kProgressMessageFieldSpacing;
+ [messageTextField_ setFrame:textFrame];
+}
+
+- (void)updateTextFieldAndResizeToWidth:(CGFloat)width {
+ NSAttributedString* title = constrained_window::GetAttributedLabelString(
+ title_,
+ ConstrainedWindow::kBoldTextFontStyle,
+ NSCenterTextAlignment,
+ NSLineBreakByWordWrapping);
+ NSAttributedString* message = constrained_window::GetAttributedLabelString(
+ message_,
+ ConstrainedWindow::kTextFontStyle,
+ NSCenterTextAlignment,
+ NSLineBreakByWordWrapping);
+ [messageTextField_ setAttributedStringValue:JoinString(title, message)];
+
+ NSRect frame = NSZeroRect;
+ frame.size.width = width;
+ [messageTextField_ setFrame:frame];
+ [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:
+ messageTextField_];
+}
+
+@end
diff --git a/chrome/browser/ui/cocoa/intents/web_intent_progress_view_controller_unittest.mm b/chrome/browser/ui/cocoa/intents/web_intent_progress_view_controller_unittest.mm
new file mode 100644
index 0000000..1d033da
--- /dev/null
+++ b/chrome/browser/ui/cocoa/intents/web_intent_progress_view_controller_unittest.mm
@@ -0,0 +1,63 @@
+// Copyright (c) 2012 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 "base/memory/scoped_nsobject.h"
+#include "base/message_loop.h"
+#import "chrome/browser/ui/cocoa/cocoa_test_helper.h"
+#import "chrome/browser/ui/cocoa/intents/web_intent_progress_view_controller.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+class WebIntentProgressViewControllerTest : public CocoaTest {
+ public:
+ WebIntentProgressViewControllerTest() {
+ view_controller_.reset([[WebIntentProgressViewController alloc] init]);
+ view_.reset([[view_controller_ view] retain]);
+ [[test_window() contentView] addSubview:view_];
+ }
+
+ protected:
+ scoped_nsobject<WebIntentProgressViewController> view_controller_;
+ scoped_nsobject<NSView> view_;
+ // Need for the progress indicator timer.
+ MessageLoop message_loop_;
+};
+
+TEST_VIEW(WebIntentProgressViewControllerTest, view_)
+
+TEST_F(WebIntentProgressViewControllerTest, Layout) {
+ const CGFloat margin = 10;
+ NSRect inner_frame = NSMakeRect(margin, margin, 100, 50);
+
+ // Layout with empty title and message.
+ NSSize empty_size =
+ [view_controller_ minimumSizeForInnerWidth:NSWidth(inner_frame)];
+ EXPECT_LE(empty_size.width, NSWidth(inner_frame));
+ [view_ setFrame:NSInsetRect(inner_frame, -margin, -margin)];
+ [view_controller_ layoutSubviewsWithinFrame:inner_frame];
+
+ // Layout with a long string that wraps.
+ NSString* string = @"A quick brown fox jumps over the lazy dog.";
+ [view_controller_ setTitle:string];
+ [view_controller_ setMessage:string];
+ NSSize new_size =
+ [view_controller_ minimumSizeForInnerWidth:NSWidth(inner_frame)];
+ EXPECT_GE(new_size.width, empty_size.width);
+ EXPECT_GT(new_size.height, empty_size.height);
+ EXPECT_EQ(NSWidth(inner_frame), new_size.width);
+ inner_frame.size.height = new_size.height;
+ [view_ setFrame:NSInsetRect(inner_frame, -margin, -margin)];
+ [view_controller_ layoutSubviewsWithinFrame:inner_frame];
+
+ // Verify that all controls are inside the inner frame.
+ for (NSView* child in [view_ subviews])
+ EXPECT_TRUE(NSContainsRect(inner_frame, [child frame]));
+}
+
+TEST_F(WebIntentProgressViewControllerTest, Progress) {
+ [view_controller_ setPercentDone:-1];
+ [view_ display];
+ [view_controller_ setPercentDone:50];
+ [view_ display];
+}
diff --git a/chrome/browser/ui/cocoa/spinner_progress_indicator.h b/chrome/browser/ui/cocoa/spinner_progress_indicator.h
new file mode 100644
index 0000000..373c12b
--- /dev/null
+++ b/chrome/browser/ui/cocoa/spinner_progress_indicator.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2012 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_UI_COCOA_SPINNER_PROGRESS_INDICATOR_
+#define CHROME_BROWSER_UI_COCOA_SPINNER_PROGRESS_INDICATOR_
+
+#import <Cocoa/Cocoa.h>
+
+#include "base/memory/scoped_ptr.h"
+#include "base/time.h"
+
+namespace base {
+class Timer;
+}
+
+// A progress indicator that draws progress as a pie chart. An terminate
+// state is represented by simply spinning a slice of the pie around the
+// control.
+@interface SpinnerProgressIndicator : NSView {
+ @private
+ scoped_ptr<base::Timer> timer_;
+ base::TimeTicks startTime_;
+ int percentDone_;
+ BOOL isIndeterminate_;
+}
+
+@property(nonatomic, assign) int percentDone;
+@property(nonatomic, assign) BOOL isIndeterminate;
+
+- (void)sizeToFit;
+
+@end
+
+#endif // CHROME_BROWSER_UI_COCOA_SPINNER_PROGRESS_INDICATOR_
diff --git a/chrome/browser/ui/cocoa/spinner_progress_indicator.mm b/chrome/browser/ui/cocoa/spinner_progress_indicator.mm
new file mode 100644
index 0000000..69afa26
--- /dev/null
+++ b/chrome/browser/ui/cocoa/spinner_progress_indicator.mm
@@ -0,0 +1,113 @@
+// Copyright (c) 2011 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/ui/cocoa/spinner_progress_indicator.h"
+
+#include "base/timer.h"
+#include "chrome/browser/download/download_util.h"
+#include "grit/theme_resources.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/canvas_skia_paint.h"
+#include "ui/gfx/rect.h"
+
+namespace {
+
+// Interval between spinner updates in miliseconds.
+const int kTimerIntervalMs = 1000 / 30;
+
+// Degrees to rotate the indeterminte spinner per second.
+const CGFloat kSpinRateDegreesPerSecond = 270;
+
+// Callback for indeterminte spinner animation timer.
+void OnTimer(SpinnerProgressIndicator* indicator) {
+ [indicator setNeedsDisplay:YES];
+}
+
+} // namespace
+
+@interface SpinnerProgressIndicator ()
+- (int)progressAngle;
+- (void)updateTimer;
+@end
+
+@implementation SpinnerProgressIndicator
+
+@synthesize percentDone = percentDone_;
+@synthesize isIndeterminate = isIndeterminate_;
+
+- (void)setPercentDone:(int)percent {
+ percentDone_ = percent;
+ [self setNeedsDisplay:YES];
+ [self updateTimer];
+}
+
+- (void)setIsIndeterminate:(BOOL)isIndeterminate {
+ isIndeterminate_ = isIndeterminate;
+ [self setNeedsDisplay:YES];
+ [self updateTimer];
+}
+
+- (void)sizeToFit {
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ gfx::ImageSkia* foreground = rb.GetImageSkiaNamed(
+ IDR_WEB_INTENT_PROGRESS_FOREGROUND);
+ NSRect frame = [self frame];
+ frame.size.width = foreground->width();
+ frame.size.height = foreground->height();
+ [self setFrame:frame];
+}
+
+- (void)drawRect:(NSRect)rect {
+ NSRect bounds = [self bounds];
+ gfx::CanvasSkiaPaint canvas(bounds, false);
+ canvas.set_composite_alpha(true);
+
+ ResourceBundle& rb = ResourceBundle::GetSharedInstance();
+ gfx::ImageSkia* background = rb.GetImageSkiaNamed(
+ IDR_WEB_INTENT_PROGRESS_BACKGROUND);
+ gfx::ImageSkia* foreground = rb.GetImageSkiaNamed(
+ IDR_WEB_INTENT_PROGRESS_FOREGROUND);
+
+ download_util::PaintCustomDownloadProgress(
+ &canvas,
+ *background,
+ *foreground,
+ foreground->width(),
+ gfx::Rect(NSRectToCGRect(bounds)),
+ [self progressAngle],
+ isIndeterminate_ ? -1 : percentDone_);
+}
+
+- (void)viewDidMoveToWindow {
+ [self updateTimer];
+}
+
+- (int)progressAngle {
+ if (!isIndeterminate_)
+ return download_util::kStartAngleDegrees;
+ base::TimeDelta delta = base::TimeTicks::Now() - startTime_;
+ int angle = delta.InSecondsF() * kSpinRateDegreesPerSecond;
+ return angle % 360;
+}
+
+- (void)updateTimer {
+ // Only run the timer if the control is in a window and showing an
+ // indeterminte progress.
+ if (![self window] || !isIndeterminate_) {
+ timer_.reset();
+ return;
+ }
+
+ if (!timer_.get()) {
+ startTime_ = base::TimeTicks::Now();
+ timer_.reset(new base::Timer(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kTimerIntervalMs),
+ base::Bind(OnTimer, self),
+ true));
+ timer_->Reset();
+ }
+}
+
+@end
diff --git a/chrome/browser/ui/cocoa/spinner_progress_indicator_unittest.mm b/chrome/browser/ui/cocoa/spinner_progress_indicator_unittest.mm
new file mode 100644
index 0000000..3fec8ab
--- /dev/null
+++ b/chrome/browser/ui/cocoa/spinner_progress_indicator_unittest.mm
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 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 "base/memory/scoped_nsobject.h"
+#include "base/message_loop.h"
+#import "chrome/browser/ui/cocoa/cocoa_test_helper.h"
+#import "chrome/browser/ui/cocoa/spinner_progress_indicator.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+class SpinnerProgressIndicatorTest : public CocoaTest {
+ public:
+ SpinnerProgressIndicatorTest() {
+ view_.reset([[SpinnerProgressIndicator alloc] initWithFrame:NSZeroRect]);
+ [view_ sizeToFit];
+ [[test_window() contentView] addSubview:view_];
+ }
+
+ protected:
+ scoped_nsobject<SpinnerProgressIndicator> view_;
+ // Need for the progress indicator timer.
+ MessageLoop message_loop_;
+};
+
+TEST_VIEW(SpinnerProgressIndicatorTest, view_)
+
+// Test determinate.
+TEST_F(SpinnerProgressIndicatorTest, Determinate) {
+ [view_ setIsIndeterminate:NO];
+ [view_ setPercentDone:0];
+ [view_ display];
+ [view_ setPercentDone:50];
+ [view_ display];
+}
+
+// Test indeterminate.
+TEST_F(SpinnerProgressIndicatorTest, Indeterminate) {
+ [view_ setIsIndeterminate:YES];
+ [view_ display];
+}
diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi
index 03455ba..46c872d 100644
--- a/chrome/chrome_browser_ui.gypi
+++ b/chrome/chrome_browser_ui.gypi
@@ -565,6 +565,8 @@
'browser/ui/cocoa/intents/web_intent_picker_cocoa2.mm',
'browser/ui/cocoa/intents/web_intent_picker_view_controller.h',
'browser/ui/cocoa/intents/web_intent_picker_view_controller.mm',
+ 'browser/ui/cocoa/intents/web_intent_progress_view_controller.h',
+ 'browser/ui/cocoa/intents/web_intent_progress_view_controller.mm',
'browser/ui/cocoa/intents/web_intent_view_controller.h',
'browser/ui/cocoa/javascript_app_modal_dialog_cocoa.h',
'browser/ui/cocoa/javascript_app_modal_dialog_cocoa.mm',
@@ -665,6 +667,8 @@
'browser/ui/cocoa/speech_recognition_bubble_cocoa.mm',
'browser/ui/cocoa/speech_recognition_window_controller.h',
'browser/ui/cocoa/speech_recognition_window_controller.mm',
+ 'browser/ui/cocoa/spinner_progress_indicator.h',
+ 'browser/ui/cocoa/spinner_progress_indicator.mm',
'browser/ui/cocoa/ssl_client_certificate_selector.mm',
'browser/ui/cocoa/status_bubble_mac.h',
'browser/ui/cocoa/status_bubble_mac.mm',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 019909e..9060c53 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -1807,6 +1807,7 @@
'browser/ui/cocoa/infobars/infobar_gradient_view_unittest.mm',
'browser/ui/cocoa/infobars/translate_infobar_unittest.mm',
'browser/ui/cocoa/intents/web_intent_message_view_controller_unittest.mm',
+ 'browser/ui/cocoa/intents/web_intent_progress_view_controller_unittest.mm',
'browser/ui/cocoa/location_bar/autocomplete_text_field_cell_unittest.mm',
'browser/ui/cocoa/location_bar/autocomplete_text_field_editor_unittest.mm',
'browser/ui/cocoa/location_bar/autocomplete_text_field_unittest.mm',
@@ -1828,6 +1829,7 @@
'browser/ui/cocoa/panels/panel_cocoa_unittest.mm',
'browser/ui/cocoa/profile_menu_controller_unittest.mm',
'browser/ui/cocoa/run_loop_testing_unittest.mm',
+ 'browser/ui/cocoa/spinner_progress_indicator_unittest.mm',
'browser/ui/cocoa/status_bubble_mac_unittest.mm',
'browser/ui/cocoa/status_icons/status_icon_mac_unittest.mm',
'browser/ui/cocoa/styled_text_field_cell_unittest.mm',