diff options
author | sail@chromium.org <sail@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-11 05:52:50 +0000 |
---|---|---|
committer | sail@chromium.org <sail@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-11 05:52:50 +0000 |
commit | d903ef8a4be2bc9ee4b1fb71f6c5b78983f8b8d6 (patch) | |
tree | b13d59e26092548d3f0f3940ce5a1aade67fb290 | |
parent | bfcade631b388e9718d7f48a571eff03d931304e (diff) | |
download | chromium_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
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', |