Chromium Code Reviews| Index: chrome/browser/ui/views/web_intent_picker_views.cc |
| diff --git a/chrome/browser/ui/views/web_intent_picker_views.cc b/chrome/browser/ui/views/web_intent_picker_views.cc |
| index f71d2f68ef491d43d89d396e37b0d363d6cf0b9c..ea64c28e9d511f0b06679ebb2e388e194f765d83 100644 |
| --- a/chrome/browser/ui/views/web_intent_picker_views.cc |
| +++ b/chrome/browser/ui/views/web_intent_picker_views.cc |
| @@ -5,6 +5,7 @@ |
| #include <algorithm> |
| #include <vector> |
| +#include "base/time.h" |
| #include "base/memory/scoped_vector.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_navigator.h" |
| @@ -27,6 +28,7 @@ |
| #include "grit/generated_resources.h" |
| #include "grit/google_chrome_strings.h" |
| #include "grit/theme_resources.h" |
| +#include "grit/ui_resources.h" |
| #include "grit/ui_resources_standard.h" |
| #include "third_party/skia/include/core/SkColor.h" |
| #include "ui/base/l10n/l10n_util.h" |
| @@ -76,6 +78,9 @@ const SkColor kHalfOpacityWhite = SkColorSetARGB(128, 255, 255, 255); |
| // The color used to display a disabled link. |
| const SkColor kDisabledLinkColor = SkColorSetRGB(128, 128, 128); |
| +// The time between successive throbber frames in milliseconds. |
| +const int kThrobberFrameTimeMs = 60; |
| + |
| // Enables or disables all child views of |view|. |
| void EnableChildViews(views::View* view, bool enabled) { |
| for (int i = 0; i < view->child_count(); ++i) { |
| @@ -125,6 +130,116 @@ StarsView::StarsView(double rating) |
| StarsView::~StarsView() { |
| } |
| +// ThrobberNativeTextButton ---------------------------------------------------- |
| + |
| +// A native text button that can display a throbber in place of its icon. Much |
| +// of the logic of this class is copied from ui/views/controls/throbber.h. |
| +class ThrobberNativeTextButton : public views::NativeTextButton { |
| + public: |
| + ThrobberNativeTextButton(views::ButtonListener* listener, |
| + const string16& text); |
| + ~ThrobberNativeTextButton(); |
|
sky
2012/04/13 21:36:16
virtual
binji
2012/04/13 22:27:33
Done.
|
| + |
| + // Start or stop the throbber. |
| + void StartThrobber(); |
| + void StopThrobber(); |
| + |
| + // Set the throbber bitmap to use. IDR_THROBBER is used by default. |
| + void SetFrames(const SkBitmap* frames); |
| + |
| + protected: |
| + virtual const SkBitmap& GetImageToPaint() const OVERRIDE; |
| + |
| + private: |
| + // The timer callback to schedule painting this view. |
| + void Run(); |
| + |
| + // Bitmap that contains the throbber frames. |
| + const SkBitmap* frames_; |
| + |
| + // The currently displayed frame, given to GetImageToPaint. |
| + mutable SkBitmap this_frame_; |
| + |
| + // How long one frame is displayed. |
| + base::TimeDelta frame_time_; |
| + |
| + // Used to schedule Run calls. |
| + base::RepeatingTimer<ThrobberNativeTextButton> timer_; |
| + |
| + // How many frames we have. |
| + int frame_count_; |
| + |
| + // Time when StartThrobber was called. |
| + base::Time start_time_; |
|
sky
2012/04/13 21:36:16
Use TimeTicks.
binji
2012/04/13 22:27:33
Done.
|
| + |
| + // Whether the throbber is shown an animating. |
| + bool running_; |
| +}; |
|
sky
2012/04/13 21:36:16
DISALLOW_
binji
2012/04/13 22:27:33
Done.
|
| + |
| +ThrobberNativeTextButton::ThrobberNativeTextButton( |
| + views::ButtonListener* listener, const string16& text) |
| + : NativeTextButton(listener, text), |
| + frame_time_(base::TimeDelta::FromMilliseconds(kThrobberFrameTimeMs)), |
| + frame_count_(0), |
| + running_(false) { |
| + SetFrames(ui::ResourceBundle::GetSharedInstance().GetImageNamed( |
| + IDR_THROBBER).ToSkBitmap()); |
| +} |
| + |
| +ThrobberNativeTextButton::~ThrobberNativeTextButton() { |
| + StopThrobber(); |
| +} |
| + |
| +void ThrobberNativeTextButton::StartThrobber() { |
| + if (running_) |
| + return; |
| + |
| + start_time_ = base::Time::Now(); |
| + timer_.Start(FROM_HERE, frame_time_ - base::TimeDelta::FromMilliseconds(10), |
|
sky
2012/04/13 21:36:16
Why the -10ms here?
binji
2012/04/13 22:27:33
Not sure, this was copy/paste from views/controls/
|
| + this, &ThrobberNativeTextButton::Run); |
| + running_ = true; |
| + |
| + SchedulePaint(); |
| +} |
| + |
| +void ThrobberNativeTextButton::StopThrobber() { |
| + if (!running_) |
| + return; |
| + |
| + timer_.Stop(); |
| + running_ = false; |
| +} |
| + |
| +void ThrobberNativeTextButton::SetFrames(const SkBitmap* frames) { |
| + frames_ = frames; |
| + DCHECK(frames_->width() > 0 && frames_->height() > 0); |
| + DCHECK(frames_->width() % frames_->height() == 0); |
| + frame_count_ = frames_->width() / frames_->height(); |
| + PreferredSizeChanged(); |
| +} |
| + |
| +const SkBitmap& ThrobberNativeTextButton::GetImageToPaint() const { |
| + if (!running_) |
| + return NativeTextButton::GetImageToPaint(); |
| + |
| + const base::TimeDelta elapsed_time = base::Time::Now() - start_time_; |
| + const int current_frame = |
| + static_cast<int>(elapsed_time / frame_time_) % frame_count_; |
|
sky
2012/04/13 21:36:16
nit: indent 4
binji
2012/04/13 22:27:33
Done.
|
| + const int image_size = frames_->height(); |
| + const int image_offset = current_frame * image_size; |
| + |
| + SkIRect subset_rect = SkIRect::MakeXYWH(image_offset, 0, |
| + image_size, image_size); |
| + frames_->extractSubset(&this_frame_, subset_rect); |
| + return this_frame_; |
| +} |
| + |
| +void ThrobberNativeTextButton::Run() { |
| + DCHECK(running_); |
| + |
| + SchedulePaint(); |
| +} |
| + |
| // ServiceButtonsView ---------------------------------------------------------- |
| // A view that contains all service buttons (i.e. the installed services). |
| @@ -148,6 +263,10 @@ class ServiceButtonsView : public views::View, |
| // Updates the service button view with new model data. |
| void Update(); |
| + // Start a throbber on the service button that will launch the service at |
| + // |url|. |
| + void StartThrobber(const GURL& url); |
| + |
| // views::ButtonListener implementation. |
| virtual void ButtonPressed(views::Button* sender, |
| const views::Event& event) OVERRIDE; |
| @@ -194,8 +313,8 @@ void ServiceButtonsView::Update() { |
| grid_layout->StartRow(0, 0); |
| - views::NativeTextButton* button = |
| - new views::NativeTextButton(this, service.title); |
| + ThrobberNativeTextButton* button = |
| + new ThrobberNativeTextButton(this, service.title); |
| button->set_alignment(views::TextButton::ALIGN_LEFT); |
| button->SetTooltipText(UTF8ToUTF16(service.url.spec().c_str())); |
| button->SetIcon(*service.favicon.ToSkBitmap()); |
| @@ -208,6 +327,20 @@ void ServiceButtonsView::Update() { |
| grid_layout->AddPaddingRow(0, views::kRelatedControlVerticalSpacing); |
| } |
| +void ServiceButtonsView::StartThrobber(const GURL& url) { |
| + for (size_t i = 0; i < model_->GetInstalledServiceCount(); ++i) { |
| + const WebIntentPickerModel::InstalledService& service = |
| + model_->GetInstalledServiceAt(i); |
| + if (service.url != url) |
| + continue; |
| + |
| + ThrobberNativeTextButton* button = |
| + static_cast<ThrobberNativeTextButton*>(child_at(i)); |
| + button->StartThrobber(); |
| + return; |
| + } |
| +} |
| + |
| void ServiceButtonsView::ButtonPressed(views::Button* sender, |
| const views::Event& event) { |
| size_t index = static_cast<size_t>(sender->tag()); |
| @@ -351,14 +484,11 @@ class SuggestedExtensionsRowView : public views::View, |
| // this extension. |
| views::Link* title_link_; |
| - // A throbber to display when the extension is being installed. |
| - views::Throbber* throbber_; |
| - |
| // The star rating of this extension. |
| StarsView* stars_; |
| // A button to install the extension. |
| - views::NativeTextButton* install_button_; |
| + ThrobberNativeTextButton* install_button_; |
| DISALLOW_COPY_AND_ASSIGN(SuggestedExtensionsRowView); |
| }; |
| @@ -380,14 +510,10 @@ SuggestedExtensionsRowView::SuggestedExtensionsRowView( |
| title_link_->set_listener(this); |
| AddChildView(title_link_); |
| - throbber_ = new views::Throbber(60, true); |
| - throbber_->SetVisible(false); |
| - AddChildView(throbber_); |
| - |
| stars_ = new StarsView(extension_->average_rating); |
| AddChildView(stars_); |
| - install_button_= new views::NativeTextButton( |
| + install_button_= new ThrobberNativeTextButton( |
| this, l10n_util::GetStringUTF16(IDS_INTENT_PICKER_INSTALL_EXTENSION)); |
| AddChildView(install_button_); |
| } |
| @@ -406,25 +532,20 @@ void SuggestedExtensionsRowView::LinkClicked(views::Link* source, |
| } |
| void SuggestedExtensionsRowView::StartThrobber() { |
| - stars_->SetVisible(false); |
| - install_button_->SetVisible(false); |
| - throbber_->SetVisible(true); |
| - throbber_->Start(); |
| - Layout(); |
| + install_button_->StartThrobber(); |
| + install_button_->SetText(string16()); |
| } |
| void SuggestedExtensionsRowView::StopThrobber() { |
| - stars_->SetVisible(true); |
| - install_button_->SetVisible(true); |
| - throbber_->SetVisible(false); |
| - throbber_->Stop(); |
| - Layout(); |
| + install_button_->StopThrobber(); |
| + install_button_->SetText( |
| + l10n_util::GetStringUTF16(IDS_INTENT_PICKER_INSTALL_EXTENSION)); |
| } |
| void SuggestedExtensionsRowView::OnEnabledChanged() { |
| title_link_->SetEnabled(enabled()); |
| - stars_->SetVisible(enabled()); |
| - install_button_->SetVisible(enabled()); |
| + stars_->SetEnabled(enabled()); |
| + install_button_->SetEnabled(enabled()); |
| View::OnEnabledChanged(); |
| Layout(); |
| } |
| @@ -439,8 +560,6 @@ void SuggestedExtensionsRowView::PaintChildren(gfx::Canvas* canvas) { |
| // A view that contains suggested extensions from the Chrome Web Store that |
| // provide an intent service matching the action/type pair. |
| -// This view also displays the "More suggestions" link which searches the |
| -// Chrome Web Store for more extensions. |
| class SuggestedExtensionsView : public views::View { |
| public: |
| SuggestedExtensionsView(const WebIntentPickerModel* model, |
| @@ -460,6 +579,9 @@ class SuggestedExtensionsView : public views::View { |
| // Hide the install throbber. This function re-enables all buttons and links. |
| void StopThrobber(); |
| + protected: |
| + virtual void OnEnabledChanged() OVERRIDE; |
| + |
| private: |
| const WebIntentPickerModel* model_; |
| SuggestedExtensionsRowView::Delegate* delegate_; |
| @@ -518,6 +640,11 @@ void SuggestedExtensionsView::StopThrobber() { |
| } |
| } |
| +void SuggestedExtensionsView::OnEnabledChanged() { |
| + EnableChildViews(this, enabled()); |
| + View::OnEnabledChanged(); |
| +} |
| + |
| } // namespace |
| // WebIntentPickerViews -------------------------------------------------------- |
| @@ -557,6 +684,8 @@ class WebIntentPickerViews : public views::ButtonListener, |
| virtual void SetActionString(const string16& action) OVERRIDE; |
| virtual void OnExtensionInstallSuccess(const std::string& id) OVERRIDE; |
| virtual void OnExtensionInstallFailure(const std::string& id) OVERRIDE; |
| + virtual void OnInlineDispositionWebContentsLoaded( |
| + content::WebContents* web_contents) OVERRIDE; |
| // WebIntentPickerModelObserver implementation. |
| virtual void OnModelChanged(WebIntentPickerModel* model) OVERRIDE; |
| @@ -625,6 +754,10 @@ class WebIntentPickerViews : public views::ButtonListener, |
| // A weak pointer to the choose another service link. |
| views::Link* choose_another_service_link_; |
| + // Set to true when displaying the inline disposition web contents. Used to |
| + // prevent laying out the inline disposition widgets twice. |
| + bool displaying_web_contents_; |
| + |
| DISALLOW_COPY_AND_ASSIGN(WebIntentPickerViews); |
| }; |
| @@ -653,7 +786,8 @@ WebIntentPickerViews::WebIntentPickerViews(Browser* browser, |
| contents_(NULL), |
| window_(NULL), |
| more_suggestions_link_(NULL), |
| - choose_another_service_link_(NULL) { |
| + choose_another_service_link_(NULL), |
| + displaying_web_contents_(false) { |
| model_->set_observer(this); |
| InitContents(); |
| @@ -733,58 +867,10 @@ void WebIntentPickerViews::OnExtensionInstallFailure(const std::string& id) { |
| // TODO(binji): What to display to user on failure? |
| } |
| -void WebIntentPickerViews::OnModelChanged(WebIntentPickerModel* model) { |
| - if (model->GetInstalledServiceCount() == 0) { |
| - suggestions_label_->SetText(l10n_util::GetStringUTF16( |
| - IDS_INTENT_PICKER_GET_MORE_SERVICES_NONE_INSTALLED)); |
| - } else { |
| - suggestions_label_->SetText( |
| - l10n_util::GetStringUTF16(IDS_INTENT_PICKER_GET_MORE_SERVICES)); |
| - } |
| - |
| - service_buttons_->Update(); |
| - extensions_->Update(); |
| - contents_->Layout(); |
| - SizeToContents(); |
| -} |
| - |
| -void WebIntentPickerViews::OnFaviconChanged( |
| - WebIntentPickerModel* model, size_t index) { |
| - service_buttons_->Update(); |
| - contents_->Layout(); |
| - SizeToContents(); |
| -} |
| - |
| -void WebIntentPickerViews::OnExtensionIconChanged( |
| - WebIntentPickerModel* model, |
| - const string16& extension_id) { |
| - extensions_->Update(); |
| - contents_->Layout(); |
| - SizeToContents(); |
| -} |
| - |
| -void WebIntentPickerViews::OnInlineDisposition( |
| - WebIntentPickerModel* model, const GURL& url) { |
| - WebContents* web_contents = WebContents::Create( |
| - browser_->profile(), NULL, MSG_ROUTING_NONE, NULL, NULL); |
| - inline_disposition_delegate_.reset(new WebIntentInlineDispositionDelegate); |
| - web_contents->SetDelegate(inline_disposition_delegate_.get()); |
| - |
| - const WebIntentPickerModel::InstalledService* service = |
| - model->GetInstalledServiceWithURL(url); |
| - DCHECK(service); |
| - |
| - // Must call this immediately after WebContents creation to avoid race |
| - // with load. |
| - delegate_->OnInlineDispositionWebContentsCreated(web_contents); |
| - |
| - TabContentsContainer* tab_contents_container = new TabContentsContainer; |
| - |
| - web_contents->GetController().LoadURL( |
| - url, |
| - content::Referrer(), |
| - content::PAGE_TRANSITION_START_PAGE, |
| - std::string()); |
| +void WebIntentPickerViews::OnInlineDispositionWebContentsLoaded( |
| + content::WebContents* web_contents) { |
| + if (displaying_web_contents_) |
| + return; |
| // Replace the picker with the inline disposition. |
| contents_->RemoveAllChildViews(true); |
| @@ -813,6 +899,9 @@ void WebIntentPickerViews::OnInlineDisposition( |
| full_cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0, |
| GridLayout::USE_PREF, 0, 0); |
| + const WebIntentPickerModel::InstalledService* service = |
| + model_->GetInstalledServiceWithURL(model_->inline_disposition_url()); |
| + |
| // Header row. |
| grid_layout->StartRow(0, 0); |
| views::ImageView* icon = new views::ImageView(); |
| @@ -834,17 +923,77 @@ void WebIntentPickerViews::OnInlineDisposition( |
| // Inline web contents row. |
| grid_layout->StartRow(0, 1); |
| + TabContentsContainer* tab_contents_container = new TabContentsContainer; |
| grid_layout->AddView(tab_contents_container, 1, 1, GridLayout::CENTER, |
| GridLayout::CENTER, kDialogMinWidth, 140); |
| // The contents can only be changed after the child is added to view |
| // hierarchy. |
| tab_contents_container->ChangeWebContents(web_contents); |
| + contents_->Layout(); |
| + SizeToContents(); |
| + displaying_web_contents_ = true; |
| +} |
| + |
| +void WebIntentPickerViews::OnModelChanged(WebIntentPickerModel* model) { |
| + if (model->GetInstalledServiceCount() == 0) { |
| + suggestions_label_->SetText(l10n_util::GetStringUTF16( |
| + IDS_INTENT_PICKER_GET_MORE_SERVICES_NONE_INSTALLED)); |
| + } else { |
| + suggestions_label_->SetText( |
| + l10n_util::GetStringUTF16(IDS_INTENT_PICKER_GET_MORE_SERVICES)); |
| + } |
| + |
| + service_buttons_->Update(); |
| + extensions_->Update(); |
| + contents_->Layout(); |
| + SizeToContents(); |
| +} |
| + |
| +void WebIntentPickerViews::OnFaviconChanged( |
| + WebIntentPickerModel* model, size_t index) { |
| + service_buttons_->Update(); |
| + contents_->Layout(); |
| + SizeToContents(); |
| +} |
| +void WebIntentPickerViews::OnExtensionIconChanged( |
| + WebIntentPickerModel* model, |
| + const string16& extension_id) { |
| + extensions_->Update(); |
| contents_->Layout(); |
| SizeToContents(); |
| } |
| +void WebIntentPickerViews::OnInlineDisposition( |
| + WebIntentPickerModel* model, const GURL& url) { |
| + WebContents* web_contents = WebContents::Create( |
| + browser_->profile(), NULL, MSG_ROUTING_NONE, NULL, NULL); |
| + inline_disposition_delegate_.reset( |
| + new WebIntentInlineDispositionDelegate(this)); |
| + web_contents->SetDelegate(inline_disposition_delegate_.get()); |
| + |
| + const WebIntentPickerModel::InstalledService* service = |
| + model->GetInstalledServiceWithURL(url); |
| + DCHECK(service); |
| + |
| + // Must call this immediately after WebContents creation to avoid race |
| + // with load. |
| + delegate_->OnInlineDispositionWebContentsCreated(web_contents); |
| + web_contents->GetController().LoadURL( |
| + url, |
| + content::Referrer(), |
| + content::PAGE_TRANSITION_START_PAGE, |
| + std::string()); |
| + |
| + // Disable all buttons and show throbber. |
| + service_buttons_->SetEnabled(false); |
| + service_buttons_->StartThrobber(url); |
| + extensions_->SetEnabled(false); |
| + more_suggestions_link_->SetEnabled(false); |
| + contents_->Layout(); |
| +} |
| + |
| void WebIntentPickerViews::OnServiceButtonClicked( |
| const WebIntentPickerModel::InstalledService& service) { |
| delegate_->OnServiceChosen(service.url, service.disposition); |