• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/views/extensions/extension_installed_bubble.h"
6 
7 #include <algorithm>
8 
9 #include "base/i18n/rtl.h"
10 #include "base/message_loop.h"
11 #include "base/utf_string_conversions.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/ui/browser.h"
14 #include "chrome/browser/ui/browser_window.h"
15 #include "chrome/browser/ui/views/browser_actions_container.h"
16 #include "chrome/browser/ui/views/frame/browser_view.h"
17 #include "chrome/browser/ui/views/location_bar/location_bar_view.h"
18 #include "chrome/browser/ui/views/toolbar_view.h"
19 #include "chrome/common/extensions/extension.h"
20 #include "chrome/common/extensions/extension_action.h"
21 #include "content/common/notification_details.h"
22 #include "content/common/notification_source.h"
23 #include "content/common/notification_type.h"
24 #include "grit/generated_resources.h"
25 #include "grit/theme_resources.h"
26 #include "ui/base/l10n/l10n_util.h"
27 #include "ui/base/resource/resource_bundle.h"
28 #include "views/controls/button/image_button.h"
29 #include "views/controls/image_view.h"
30 #include "views/controls/label.h"
31 #include "views/layout/layout_constants.h"
32 #include "views/view.h"
33 
34 namespace {
35 
36 const int kIconSize = 43;
37 
38 const int kRightColumnWidth = 285;
39 
40 // The Bubble uses a BubbleBorder which adds about 6 pixels of whitespace
41 // around the content view. We compensate by reducing our outer borders by this
42 // amount + 4px.
43 const int kOuterMarginInset = 10;
44 const int kHorizOuterMargin = views::kPanelHorizMargin - kOuterMarginInset;
45 const int kVertOuterMargin = views::kPanelVertMargin - kOuterMarginInset;
46 
47 // Interior vertical margin is 8px smaller than standard
48 const int kVertInnerMargin = views::kPanelVertMargin - 8;
49 
50 // The image we use for the close button has three pixels of whitespace padding.
51 const int kCloseButtonPadding = 3;
52 
53 // We want to shift the right column (which contains the header and text) up
54 // 4px to align with icon.
55 const int kRightcolumnVerticalShift = -4;
56 
57 // How long to wait for browser action animations to complete before retrying.
58 const int kAnimationWaitTime = 50;
59 
60 // How often we retry when waiting for browser action animation to end.
61 const int kAnimationWaitMaxRetry = 10;
62 
63 }  // namespace
64 
65 namespace browser {
66 
ShowExtensionInstalledBubble(const Extension * extension,Browser * browser,const SkBitmap & icon,Profile * profile)67 void ShowExtensionInstalledBubble(
68     const Extension* extension,
69     Browser* browser,
70     const SkBitmap& icon,
71     Profile* profile) {
72   ExtensionInstalledBubble::Show(extension, browser, icon);
73 }
74 
75 } // namespace browser
76 
77 // InstalledBubbleContent is the content view which is placed in the
78 // ExtensionInstalledBubble. It displays the install icon and explanatory
79 // text about the installed extension.
80 class InstalledBubbleContent : public views::View,
81                                public views::ButtonListener {
82  public:
InstalledBubbleContent(const Extension * extension,ExtensionInstalledBubble::BubbleType type,SkBitmap * icon)83   InstalledBubbleContent(const Extension* extension,
84                          ExtensionInstalledBubble::BubbleType type,
85                          SkBitmap* icon)
86       : bubble_(NULL),
87         type_(type),
88         info_(NULL) {
89     ResourceBundle& rb = ResourceBundle::GetSharedInstance();
90     const gfx::Font& font = rb.GetFont(ResourceBundle::BaseFont);
91 
92     // Scale down to 43x43, but allow smaller icons (don't scale up).
93     gfx::Size size(icon->width(), icon->height());
94     if (size.width() > kIconSize || size.height() > kIconSize)
95       size = gfx::Size(kIconSize, kIconSize);
96     icon_ = new views::ImageView();
97     icon_->SetImageSize(size);
98     icon_->SetImage(*icon);
99     AddChildView(icon_);
100 
101     string16 extension_name = UTF8ToUTF16(extension->name());
102     base::i18n::AdjustStringForLocaleDirection(&extension_name);
103     heading_ = new views::Label(UTF16ToWide(
104         l10n_util::GetStringFUTF16(IDS_EXTENSION_INSTALLED_HEADING,
105                                    extension_name)));
106     heading_->SetFont(rb.GetFont(ResourceBundle::MediumFont));
107     heading_->SetMultiLine(true);
108     heading_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
109     AddChildView(heading_);
110 
111     if (type_ == ExtensionInstalledBubble::PAGE_ACTION) {
112       info_ = new views::Label(UTF16ToWide(l10n_util::GetStringUTF16(
113           IDS_EXTENSION_INSTALLED_PAGE_ACTION_INFO)));
114       info_->SetFont(font);
115       info_->SetMultiLine(true);
116       info_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
117       AddChildView(info_);
118     }
119 
120     if (type_ == ExtensionInstalledBubble::OMNIBOX_KEYWORD) {
121       info_ = new views::Label(UTF16ToWide(l10n_util::GetStringFUTF16(
122           IDS_EXTENSION_INSTALLED_OMNIBOX_KEYWORD_INFO,
123           UTF8ToUTF16(extension->omnibox_keyword()))));
124       info_->SetFont(font);
125       info_->SetMultiLine(true);
126       info_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
127       AddChildView(info_);
128     }
129 
130     manage_ = new views::Label(UTF16ToWide(
131         l10n_util::GetStringUTF16(IDS_EXTENSION_INSTALLED_MANAGE_INFO)));
132     manage_->SetFont(font);
133     manage_->SetMultiLine(true);
134     manage_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
135     AddChildView(manage_);
136 
137     close_button_ = new views::ImageButton(this);
138     close_button_->SetImage(views::CustomButton::BS_NORMAL,
139         rb.GetBitmapNamed(IDR_CLOSE_BAR));
140     close_button_->SetImage(views::CustomButton::BS_HOT,
141         rb.GetBitmapNamed(IDR_CLOSE_BAR_H));
142     close_button_->SetImage(views::CustomButton::BS_PUSHED,
143         rb.GetBitmapNamed(IDR_CLOSE_BAR_P));
144     AddChildView(close_button_);
145   }
146 
set_bubble(Bubble * bubble)147   void set_bubble(Bubble* bubble) { bubble_ = bubble; }
148 
ButtonPressed(views::Button * sender,const views::Event & event)149   virtual void ButtonPressed(
150       views::Button* sender,
151       const views::Event& event) {
152     if (sender == close_button_) {
153       bubble_->set_fade_away_on_close(true);
154       GetWidget()->Close();
155     } else {
156       NOTREACHED() << "Unknown view";
157     }
158   }
159 
160  private:
GetPreferredSize()161   virtual gfx::Size GetPreferredSize() {
162     int width = kHorizOuterMargin;
163     width += kIconSize;
164     width += views::kPanelHorizMargin;
165     width += kRightColumnWidth;
166     width += 2 * views::kPanelHorizMargin;
167     width += kHorizOuterMargin;
168 
169     int height = kVertOuterMargin;
170     height += heading_->GetHeightForWidth(kRightColumnWidth);
171     height += kVertInnerMargin;
172     if (type_ == ExtensionInstalledBubble::PAGE_ACTION ||
173         type_ == ExtensionInstalledBubble::OMNIBOX_KEYWORD) {
174       height += info_->GetHeightForWidth(kRightColumnWidth);
175       height += kVertInnerMargin;
176     }
177     height += manage_->GetHeightForWidth(kRightColumnWidth);
178     height += kVertOuterMargin;
179 
180     return gfx::Size(width, std::max(height, kIconSize + 2 * kVertOuterMargin));
181   }
182 
Layout()183   virtual void Layout() {
184     int x = kHorizOuterMargin;
185     int y = kVertOuterMargin;
186 
187     icon_->SetBounds(x, y, kIconSize, kIconSize);
188     x += kIconSize;
189     x += views::kPanelHorizMargin;
190 
191     y += kRightcolumnVerticalShift;
192     heading_->SizeToFit(kRightColumnWidth);
193     heading_->SetX(x);
194     heading_->SetY(y);
195     y += heading_->height();
196     y += kVertInnerMargin;
197 
198     if (type_ == ExtensionInstalledBubble::PAGE_ACTION ||
199         type_ == ExtensionInstalledBubble::OMNIBOX_KEYWORD) {
200       info_->SizeToFit(kRightColumnWidth);
201       info_->SetX(x);
202       info_->SetY(y);
203       y += info_->height();
204       y += kVertInnerMargin;
205     }
206 
207     manage_->SizeToFit(kRightColumnWidth);
208     manage_->SetX(x);
209     manage_->SetY(y);
210     y += manage_->height();
211     y += kVertInnerMargin;
212 
213     gfx::Size sz;
214     x += kRightColumnWidth + 2 * views::kPanelHorizMargin + kHorizOuterMargin -
215         close_button_->GetPreferredSize().width();
216     y = kVertOuterMargin;
217     sz = close_button_->GetPreferredSize();
218     // x-1 & y-1 is just slop to get the close button visually aligned with the
219     // title text and bubble arrow.
220     close_button_->SetBounds(x - 1, y - 1, sz.width(), sz.height());
221   }
222 
223   // The Bubble showing us.
224   Bubble* bubble_;
225 
226   ExtensionInstalledBubble::BubbleType type_;
227   views::ImageView* icon_;
228   views::Label* heading_;
229   views::Label* info_;
230   views::Label* manage_;
231   views::ImageButton* close_button_;
232 
233   DISALLOW_COPY_AND_ASSIGN(InstalledBubbleContent);
234 };
235 
Show(const Extension * extension,Browser * browser,const SkBitmap & icon)236 void ExtensionInstalledBubble::Show(const Extension* extension,
237                                     Browser *browser,
238                                     const SkBitmap& icon) {
239   new ExtensionInstalledBubble(extension, browser, icon);
240 }
241 
ExtensionInstalledBubble(const Extension * extension,Browser * browser,const SkBitmap & icon)242 ExtensionInstalledBubble::ExtensionInstalledBubble(const Extension* extension,
243                                                    Browser *browser,
244                                                    const SkBitmap& icon)
245     : extension_(extension),
246       browser_(browser),
247       icon_(icon),
248       animation_wait_retries_(0) {
249   AddRef();  // Balanced in BubbleClosing.
250 
251   if (!extension_->omnibox_keyword().empty()) {
252     type_ = OMNIBOX_KEYWORD;
253   } else if (extension_->browser_action()) {
254     type_ = BROWSER_ACTION;
255   } else if (extension->page_action() &&
256              !extension->page_action()->default_icon_path().empty()) {
257     type_ = PAGE_ACTION;
258   } else {
259     type_ = GENERIC;
260   }
261 
262   // |extension| has been initialized but not loaded at this point. We need
263   // to wait on showing the Bubble until not only the EXTENSION_LOADED gets
264   // fired, but all of the EXTENSION_LOADED Observers have run. Only then can we
265   // be sure that a BrowserAction or PageAction has had views created which we
266   // can inspect for the purpose of previewing of pointing to them.
267   registrar_.Add(this, NotificationType::EXTENSION_LOADED,
268       Source<Profile>(browser->profile()));
269   registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
270       Source<Profile>(browser->profile()));
271 }
272 
~ExtensionInstalledBubble()273 ExtensionInstalledBubble::~ExtensionInstalledBubble() {}
274 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)275 void ExtensionInstalledBubble::Observe(NotificationType type,
276                                        const NotificationSource& source,
277                                        const NotificationDetails& details) {
278   if (type == NotificationType::EXTENSION_LOADED) {
279     const Extension* extension = Details<const Extension>(details).ptr();
280     if (extension == extension_) {
281       animation_wait_retries_ = 0;
282       // PostTask to ourself to allow all EXTENSION_LOADED Observers to run.
283       MessageLoopForUI::current()->PostTask(FROM_HERE, NewRunnableMethod(this,
284           &ExtensionInstalledBubble::ShowInternal));
285     }
286   } else if (type == NotificationType::EXTENSION_UNLOADED) {
287     const Extension* extension =
288         Details<UnloadedExtensionInfo>(details)->extension;
289     if (extension == extension_)
290       extension_ = NULL;
291   } else {
292     NOTREACHED() << L"Received unexpected notification";
293   }
294 }
295 
ShowInternal()296 void ExtensionInstalledBubble::ShowInternal() {
297   BrowserView* browser_view = BrowserView::GetBrowserViewForNativeWindow(
298       browser_->window()->GetNativeHandle());
299 
300   const views::View* reference_view = NULL;
301   if (type_ == BROWSER_ACTION) {
302     BrowserActionsContainer* container =
303         browser_view->GetToolbarView()->browser_actions();
304     if (container->animating() &&
305         animation_wait_retries_++ < kAnimationWaitMaxRetry) {
306       // We don't know where the view will be until the container has stopped
307       // animating, so check back in a little while.
308       MessageLoopForUI::current()->PostDelayedTask(
309           FROM_HERE, NewRunnableMethod(this,
310           &ExtensionInstalledBubble::ShowInternal), kAnimationWaitTime);
311       return;
312     }
313     reference_view = container->GetBrowserActionView(
314         extension_->browser_action());
315     // If the view is not visible then it is in the chevron, so point the
316     // install bubble to the chevron instead. If this is an incognito window,
317     // both could be invisible.
318     if (!reference_view || !reference_view->IsVisible()) {
319       reference_view = container->chevron();
320       if (!reference_view || !reference_view->IsVisible())
321         reference_view = NULL;  // fall back to app menu below.
322     }
323   } else if (type_ == PAGE_ACTION) {
324     LocationBarView* location_bar_view = browser_view->GetLocationBarView();
325     location_bar_view->SetPreviewEnabledPageAction(extension_->page_action(),
326                                                    true);  // preview_enabled
327     reference_view = location_bar_view->GetPageActionView(
328         extension_->page_action());
329     DCHECK(reference_view);
330   } else if (type_ == OMNIBOX_KEYWORD) {
331     LocationBarView* location_bar_view = browser_view->GetLocationBarView();
332     reference_view = location_bar_view;
333     DCHECK(reference_view);
334   }
335 
336   // Default case.
337   if (reference_view == NULL)
338     reference_view = browser_view->GetToolbarView()->app_menu();
339 
340   gfx::Point origin;
341   views::View::ConvertPointToScreen(reference_view, &origin);
342   gfx::Rect bounds = reference_view->bounds();
343   bounds.set_origin(origin);
344   BubbleBorder::ArrowLocation arrow_location = BubbleBorder::TOP_RIGHT;
345 
346   // For omnibox keyword bubbles, move the arrow to point to the left edge
347   // of the omnibox, just to the right of the icon.
348   if (type_ == OMNIBOX_KEYWORD) {
349     bounds.set_origin(
350         browser_view->GetLocationBarView()->GetLocationEntryOrigin());
351     bounds.set_width(0);
352     arrow_location = BubbleBorder::TOP_LEFT;
353   }
354 
355   bubble_content_ = new InstalledBubbleContent(extension_, type_, &icon_);
356   Bubble* bubble = Bubble::Show(browser_view->GetWidget(), bounds,
357                                 arrow_location, bubble_content_, this);
358   bubble_content_->set_bubble(bubble);
359 }
360 
361 // BubbleDelegate
BubbleClosing(Bubble * bubble,bool closed_by_escape)362 void ExtensionInstalledBubble::BubbleClosing(Bubble* bubble,
363                                              bool closed_by_escape) {
364   if (extension_ && type_ == PAGE_ACTION) {
365     BrowserView* browser_view = BrowserView::GetBrowserViewForNativeWindow(
366         browser_->window()->GetNativeHandle());
367     browser_view->GetLocationBarView()->SetPreviewEnabledPageAction(
368         extension_->page_action(),
369         false);  // preview_enabled
370   }
371 
372   Release();  // Balanced in ctor.
373 }
374 
CloseOnEscape()375 bool ExtensionInstalledBubble::CloseOnEscape() {
376   return true;
377 }
378 
FadeInOnShow()379 bool ExtensionInstalledBubble::FadeInOnShow() {
380   return true;
381 }
382