Home | History | Annotate | Download | only in views
      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/first_run_bubble.h"
      6 
      7 #include "base/utf_string_conversions.h"
      8 #include "chrome/browser/first_run/first_run.h"
      9 #include "chrome/browser/metrics/user_metrics.h"
     10 #include "chrome/browser/search_engines/util.h"
     11 #include "chrome/browser/ui/browser.h"
     12 #include "chrome/browser/ui/browser_list.h"
     13 #include "chrome/browser/ui/browser_window.h"
     14 #include "grit/chromium_strings.h"
     15 #include "grit/generated_resources.h"
     16 #include "grit/locale_settings.h"
     17 #include "grit/theme_resources.h"
     18 #include "ui/base/l10n/l10n_font_util.h"
     19 #include "ui/base/l10n/l10n_util.h"
     20 #include "ui/base/resource/resource_bundle.h"
     21 #include "views/controls/button/image_button.h"
     22 #include "views/controls/button/native_button.h"
     23 #include "views/controls/label.h"
     24 #include "views/events/event.h"
     25 #include "views/focus/focus_manager.h"
     26 #include "views/layout/layout_constants.h"
     27 #include "views/widget/widget_win.h"
     28 #include "views/window/window.h"
     29 
     30 namespace {
     31 
     32 // How much extra padding to put around our content over what the Bubble
     33 // provides.
     34 const int kBubblePadding = 4;
     35 
     36 // How much extra padding to put around our content over what the Bubble
     37 // provides in alternative OEM bubble.
     38 const int kOEMBubblePadding = 4;
     39 
     40 // Padding between parts of strings on the same line (for instance,
     41 // "New!" and "Search from the address bar!"
     42 const int kStringSeparationPadding = 2;
     43 
     44 // Margin around close button.
     45 const int kMarginRightOfCloseButton = 7;
     46 
     47 }  // namespace
     48 
     49 // Base class for implementations of the client view which appears inside the
     50 // first run bubble. It is a dialog-ish view, but is not a true dialog.
     51 class FirstRunBubbleViewBase : public views::View,
     52                                public views::ButtonListener,
     53                                public views::FocusChangeListener {
     54  public:
     55   // Called by FirstRunBubble::Show to request focus for the proper button
     56   // in the FirstRunBubbleView when it is shown.
     57   virtual void BubbleShown() = 0;
     58 };
     59 
     60 // FirstRunBubbleView ---------------------------------------------------------
     61 
     62 class FirstRunBubbleView : public FirstRunBubbleViewBase {
     63  public:
     64   FirstRunBubbleView(FirstRunBubble* bubble_window, Profile* profile);
     65 
     66  private:
     67   virtual ~FirstRunBubbleView() {}
     68 
     69   // FirstRunBubbleViewBase:
     70   virtual void BubbleShown();
     71 
     72   // Overridden from View:
     73   virtual void ButtonPressed(views::Button* sender, const views::Event& event);
     74   virtual void Layout();
     75   virtual gfx::Size GetPreferredSize();
     76 
     77   // FocusChangeListener:
     78   virtual void FocusWillChange(View* focused_before, View* focused_now);
     79 
     80   FirstRunBubble* bubble_window_;
     81   views::Label* label1_;
     82   views::Label* label2_;
     83   views::Label* label3_;
     84   views::NativeButton* change_button_;
     85   views::NativeButton* keep_button_;
     86   Profile* profile_;
     87 
     88   DISALLOW_COPY_AND_ASSIGN(FirstRunBubbleView);
     89 };
     90 
     91 FirstRunBubbleView::FirstRunBubbleView(FirstRunBubble* bubble_window,
     92                                        Profile* profile)
     93     : bubble_window_(bubble_window),
     94       label1_(NULL),
     95       label2_(NULL),
     96       label3_(NULL),
     97       keep_button_(NULL),
     98       change_button_(NULL),
     99       profile_(profile) {
    100   const gfx::Font& font =
    101       ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::MediumFont);
    102 
    103   label1_ = new views::Label(
    104       UTF16ToWide(l10n_util::GetStringUTF16(IDS_FR_BUBBLE_TITLE)));
    105   label1_->SetFont(font.DeriveFont(3, gfx::Font::BOLD));
    106   label1_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
    107   AddChildView(label1_);
    108 
    109   gfx::Size ps = GetPreferredSize();
    110 
    111   label2_ = new views::Label(
    112       UTF16ToWide(l10n_util::GetStringUTF16(IDS_FR_BUBBLE_SUBTEXT)));
    113   label2_->SetMultiLine(true);
    114   label2_->SetFont(font);
    115   label2_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
    116   label2_->SizeToFit(ps.width() - kBubblePadding * 2);
    117   AddChildView(label2_);
    118 
    119   std::wstring question_str = UTF16ToWide(l10n_util::GetStringFUTF16(
    120       IDS_FR_BUBBLE_QUESTION,
    121       GetDefaultSearchEngineName(profile)));
    122   label3_ = new views::Label(question_str);
    123   label3_->SetMultiLine(true);
    124   label3_->SetFont(font);
    125   label3_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
    126   label3_->SizeToFit(ps.width() - kBubblePadding * 2);
    127   AddChildView(label3_);
    128 
    129   std::wstring keep_str = UTF16ToWide(l10n_util::GetStringFUTF16(
    130       IDS_FR_BUBBLE_OK,
    131       GetDefaultSearchEngineName(profile)));
    132   keep_button_ = new views::NativeButton(this, keep_str);
    133   keep_button_->SetIsDefault(true);
    134   AddChildView(keep_button_);
    135 
    136   std::wstring change_str =
    137       UTF16ToWide(l10n_util::GetStringUTF16(IDS_FR_BUBBLE_CHANGE));
    138   change_button_ = new views::NativeButton(this, change_str);
    139   AddChildView(change_button_);
    140 }
    141 
    142 void FirstRunBubbleView::BubbleShown() {
    143   keep_button_->RequestFocus();
    144 }
    145 
    146 void FirstRunBubbleView::ButtonPressed(views::Button* sender,
    147                                        const views::Event& event) {
    148   UserMetrics::RecordAction(UserMetricsAction("FirstRunBubbleView_Clicked"),
    149                             profile_);
    150   bubble_window_->set_fade_away_on_close(true);
    151   bubble_window_->Close();
    152   if (change_button_ == sender) {
    153     UserMetrics::RecordAction(
    154                     UserMetricsAction("FirstRunBubbleView_ChangeButton"),
    155                     profile_);
    156 
    157     Browser* browser = BrowserList::GetLastActive();
    158     if (browser) {
    159       browser->OpenSearchEngineOptionsDialog();
    160     }
    161   }
    162 }
    163 
    164 void FirstRunBubbleView::Layout() {
    165   gfx::Size canvas = GetPreferredSize();
    166 
    167   // The multiline business that follows is dirty hacks to get around
    168   // bug 1325257.
    169   label1_->SetMultiLine(false);
    170   gfx::Size pref_size = label1_->GetPreferredSize();
    171   label1_->SetMultiLine(true);
    172   label1_->SizeToFit(canvas.width() - kBubblePadding * 2);
    173   label1_->SetBounds(kBubblePadding, kBubblePadding,
    174                      canvas.width() - kBubblePadding * 2,
    175                      pref_size.height());
    176 
    177   int next_v_space = label1_->y() + pref_size.height() +
    178                      views::kRelatedControlSmallVerticalSpacing;
    179 
    180   pref_size = label2_->GetPreferredSize();
    181   label2_->SetBounds(kBubblePadding, next_v_space,
    182                      canvas.width() - kBubblePadding * 2,
    183                      pref_size.height());
    184 
    185   next_v_space = label2_->y() + label2_->height() +
    186                  views::kPanelSubVerticalSpacing;
    187 
    188   pref_size = label3_->GetPreferredSize();
    189   label3_->SetBounds(kBubblePadding, next_v_space,
    190                      canvas.width() - kBubblePadding * 2,
    191                      pref_size.height());
    192 
    193   pref_size = change_button_->GetPreferredSize();
    194   change_button_->SetBounds(
    195       canvas.width() - pref_size.width() - kBubblePadding,
    196       canvas.height() - pref_size.height() - views::kButtonVEdgeMargin,
    197       pref_size.width(), pref_size.height());
    198 
    199   pref_size = keep_button_->GetPreferredSize();
    200   keep_button_->SetBounds(change_button_->x() - pref_size.width() -
    201                           views::kRelatedButtonHSpacing, change_button_->y(),
    202                           pref_size.width(), pref_size.height());
    203 }
    204 
    205 gfx::Size FirstRunBubbleView::GetPreferredSize() {
    206   return gfx::Size(views::Window::GetLocalizedContentsSize(
    207       IDS_FIRSTRUNBUBBLE_DIALOG_WIDTH_CHARS,
    208       IDS_FIRSTRUNBUBBLE_DIALOG_HEIGHT_LINES));
    209 }
    210 
    211 void FirstRunBubbleView::FocusWillChange(View* focused_before,
    212                                          View* focused_now) {
    213   if (focused_before &&
    214       (focused_before->GetClassName() == views::NativeButton::kViewClassName)) {
    215     views::NativeButton* before =
    216         static_cast<views::NativeButton*>(focused_before);
    217     before->SetIsDefault(false);
    218   }
    219   if (focused_now &&
    220       (focused_now->GetClassName() == views::NativeButton::kViewClassName)) {
    221     views::NativeButton* after = static_cast<views::NativeButton*>(focused_now);
    222     after->SetIsDefault(true);
    223   }
    224 }
    225 
    226 // FirstRunOEMBubbleView ------------------------------------------------------
    227 
    228 class FirstRunOEMBubbleView : public FirstRunBubbleViewBase {
    229  public:
    230   FirstRunOEMBubbleView(FirstRunBubble* bubble_window, Profile* profile);
    231 
    232  private:
    233   virtual ~FirstRunOEMBubbleView() { }
    234 
    235   // FirstRunBubbleViewBase:
    236   virtual void BubbleShown();
    237 
    238   // Overridden from View:
    239   virtual void ButtonPressed(views::Button* sender, const views::Event& event);
    240   virtual void Layout();
    241   virtual gfx::Size GetPreferredSize();
    242 
    243   // FocusChangeListener:
    244   virtual void FocusWillChange(View* focused_before, View* focused_now);
    245 
    246   FirstRunBubble* bubble_window_;
    247   views::Label* label1_;
    248   views::Label* label2_;
    249   views::Label* label3_;
    250   views::ImageButton* close_button_;
    251   Profile* profile_;
    252 
    253   DISALLOW_COPY_AND_ASSIGN(FirstRunOEMBubbleView);
    254 };
    255 
    256 FirstRunOEMBubbleView::FirstRunOEMBubbleView(FirstRunBubble* bubble_window,
    257                                              Profile* profile)
    258     : bubble_window_(bubble_window),
    259       label1_(NULL),
    260       label2_(NULL),
    261       label3_(NULL),
    262       close_button_(NULL),
    263       profile_(profile) {
    264   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    265   const gfx::Font& font = rb.GetFont(ResourceBundle::MediumFont);
    266 
    267   label1_ = new views::Label(
    268       UTF16ToWide(l10n_util::GetStringUTF16(IDS_FR_OEM_BUBBLE_TITLE_1)));
    269   label1_->SetFont(font.DeriveFont(3, gfx::Font::BOLD));
    270   label1_->SetColor(SK_ColorRED);
    271   label1_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
    272   AddChildView(label1_);
    273 
    274   label2_ = new views::Label(
    275       UTF16ToWide(l10n_util::GetStringUTF16(IDS_FR_OEM_BUBBLE_TITLE_2)));
    276   label2_->SetFont(font.DeriveFont(3, gfx::Font::BOLD));
    277   label2_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
    278   AddChildView(label2_);
    279 
    280   gfx::Size ps = GetPreferredSize();
    281 
    282   label3_ = new views::Label(
    283       UTF16ToWide(l10n_util::GetStringUTF16(IDS_FR_OEM_BUBBLE_SUBTEXT)));
    284   label3_->SetMultiLine(true);
    285   label3_->SetFont(font);
    286   label3_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
    287   label3_->SizeToFit(ps.width() - kOEMBubblePadding * 2);
    288   AddChildView(label3_);
    289 
    290   close_button_ = new views::ImageButton(this);
    291   close_button_->SetImage(views::CustomButton::BS_NORMAL,
    292                           rb.GetBitmapNamed(IDR_CLOSE_BAR));
    293   close_button_->SetImage(views::CustomButton::BS_HOT,
    294                           rb.GetBitmapNamed(IDR_CLOSE_BAR_H));
    295   close_button_->SetImage(views::CustomButton::BS_PUSHED,
    296                           rb.GetBitmapNamed(IDR_CLOSE_BAR_P));
    297 
    298   AddChildView(close_button_);
    299 }
    300 
    301 void FirstRunOEMBubbleView::BubbleShown() {
    302   RequestFocus();
    303   // No button in oem_bubble to request focus.
    304 }
    305 
    306 void FirstRunOEMBubbleView::ButtonPressed(views::Button* sender,
    307                                           const views::Event& event) {
    308   UserMetrics::RecordAction(UserMetricsAction("FirstRunOEMBubbleView_Clicked"),
    309                             profile_);
    310   bubble_window_->set_fade_away_on_close(true);
    311   bubble_window_->Close();
    312 }
    313 
    314 void FirstRunOEMBubbleView::Layout() {
    315   gfx::Size canvas = GetPreferredSize();
    316 
    317   // First, draw the close button on the far right.
    318   gfx::Size sz = close_button_->GetPreferredSize();
    319   close_button_->SetBounds(
    320       canvas.width() - sz.width() - kMarginRightOfCloseButton,
    321       kOEMBubblePadding, sz.width(), sz.height());
    322 
    323   gfx::Size pref_size = label1_->GetPreferredSize();
    324   label1_->SetBounds(kOEMBubblePadding, kOEMBubblePadding,
    325                      pref_size.width() + kOEMBubblePadding * 2,
    326                      pref_size.height());
    327 
    328   pref_size = label2_->GetPreferredSize();
    329   label2_->SetBounds(
    330       kOEMBubblePadding * 2 + label1_->GetPreferredSize().width(),
    331       kOEMBubblePadding, canvas.width() - kOEMBubblePadding * 2,
    332       pref_size.height());
    333 
    334   int next_v_space =
    335       label1_->y() + pref_size.height() +
    336           views::kRelatedControlSmallVerticalSpacing;
    337 
    338   pref_size = label3_->GetPreferredSize();
    339   label3_->SetBounds(kOEMBubblePadding, next_v_space,
    340                      canvas.width() - kOEMBubblePadding * 2,
    341                      pref_size.height());
    342 }
    343 
    344 gfx::Size FirstRunOEMBubbleView::GetPreferredSize() {
    345   // Calculate width based on font and text.
    346   ResourceBundle& rb = ResourceBundle::GetSharedInstance();
    347   const gfx::Font& font = rb.GetFont(
    348       ResourceBundle::MediumFont).DeriveFont(3, gfx::Font::BOLD);
    349   gfx::Size size = gfx::Size(
    350       ui::GetLocalizedContentsWidthForFont(
    351           IDS_FIRSTRUNOEMBUBBLE_DIALOG_WIDTH_CHARS, font),
    352       ui::GetLocalizedContentsHeightForFont(
    353           IDS_FIRSTRUNOEMBUBBLE_DIALOG_HEIGHT_LINES, font));
    354 
    355   // WARNING: HACK. Vista and XP calculate font size differently; this means
    356   // that a dialog box correctly proportioned for XP will appear too large in
    357   // Vista. The correct thing to do is to change font size calculations in
    358   // XP or Vista so that the length of a string is calculated properly. For
    359   // now, we force Vista to show a correctly-sized box by taking account of
    360   // the difference in font size calculation. The coefficient should not be
    361   // stored in a variable because it's a hack and should go away.
    362   if (views::WidgetWin::IsAeroGlassEnabled()) {
    363     size.set_width(static_cast<int>(size.width() * 0.85));
    364     size.set_height(static_cast<int>(size.height() * 0.85));
    365   }
    366   return size;
    367 }
    368 
    369 void FirstRunOEMBubbleView::FocusWillChange(View* focused_before,
    370                                             View* focused_now) {
    371   // No buttons in oem_bubble to register focus changes.
    372 }
    373 
    374 // FirstRunMinimalBubbleView --------------------------------------------------
    375 // TODO(mirandac): combine FRBubbles more elegantly.  http://crbug.com/41353
    376 
    377 class FirstRunMinimalBubbleView : public FirstRunBubbleViewBase {
    378  public:
    379   FirstRunMinimalBubbleView(FirstRunBubble* bubble_window, Profile* profile);
    380 
    381  private:
    382   virtual ~FirstRunMinimalBubbleView() { }
    383 
    384   // FirstRunBubbleViewBase:
    385   virtual void BubbleShown();
    386 
    387   // Overridden from View:
    388   virtual void ButtonPressed(views::Button* sender,
    389                              const views::Event& event) { }
    390   virtual void Layout();
    391   virtual gfx::Size GetPreferredSize();
    392 
    393   // FocusChangeListener:
    394   virtual void FocusWillChange(View* focused_before, View* focused_now);
    395 
    396   FirstRunBubble* bubble_window_;
    397   Profile* profile_;
    398   views::Label* label1_;
    399   views::Label* label2_;
    400 
    401   DISALLOW_COPY_AND_ASSIGN(FirstRunMinimalBubbleView);
    402 };
    403 
    404 FirstRunMinimalBubbleView::FirstRunMinimalBubbleView(
    405     FirstRunBubble* bubble_window,
    406     Profile* profile)
    407     : bubble_window_(bubble_window),
    408       profile_(profile),
    409       label1_(NULL),
    410       label2_(NULL) {
    411   const gfx::Font& font =
    412       ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::MediumFont);
    413 
    414   label1_ = new views::Label(UTF16ToWide(l10n_util::GetStringFUTF16(
    415       IDS_FR_SE_BUBBLE_TITLE,
    416       GetDefaultSearchEngineName(profile_))));
    417   label1_->SetFont(font.DeriveFont(3, gfx::Font::BOLD));
    418   label1_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
    419   AddChildView(label1_);
    420 
    421   gfx::Size ps = GetPreferredSize();
    422 
    423   label2_ = new views::Label(
    424       UTF16ToWide(l10n_util::GetStringUTF16(IDS_FR_BUBBLE_SUBTEXT)));
    425   label2_->SetMultiLine(true);
    426   label2_->SetFont(font);
    427   label2_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
    428   label2_->SizeToFit(ps.width() - kBubblePadding * 2);
    429   AddChildView(label2_);
    430 }
    431 
    432 void FirstRunMinimalBubbleView::BubbleShown() {
    433   RequestFocus();
    434 }
    435 
    436 void FirstRunMinimalBubbleView::Layout() {
    437   gfx::Size canvas = GetPreferredSize();
    438 
    439   // See comments in FirstRunOEMBubbleView::Layout explaining this hack.
    440   label1_->SetMultiLine(false);
    441   gfx::Size pref_size = label1_->GetPreferredSize();
    442   label1_->SetMultiLine(true);
    443   label1_->SizeToFit(canvas.width() - kBubblePadding * 2);
    444   label1_->SetBounds(kBubblePadding, kBubblePadding,
    445                      canvas.width() - kBubblePadding * 2,
    446                      pref_size.height());
    447 
    448   int next_v_space = label1_->y() + pref_size.height() +
    449                      views::kRelatedControlSmallVerticalSpacing;
    450 
    451   pref_size = label2_->GetPreferredSize();
    452   label2_->SetBounds(kBubblePadding, next_v_space,
    453                      canvas.width() - kBubblePadding * 2,
    454                      pref_size.height());
    455 }
    456 
    457 gfx::Size FirstRunMinimalBubbleView::GetPreferredSize() {
    458   return gfx::Size(views::Window::GetLocalizedContentsSize(
    459       IDS_FIRSTRUN_MINIMAL_BUBBLE_DIALOG_WIDTH_CHARS,
    460       IDS_FIRSTRUN_MINIMAL_BUBBLE_DIALOG_HEIGHT_LINES));
    461 }
    462 
    463 void FirstRunMinimalBubbleView::FocusWillChange(View* focused_before,
    464                                                 View* focused_now) {
    465   // No buttons in minimal bubble to register focus changes.
    466 }
    467 
    468 
    469 // FirstRunBubble -------------------------------------------------------------
    470 
    471 // static
    472 FirstRunBubble* FirstRunBubble::Show(Profile* profile,
    473                                      views::Widget* parent,
    474                                      const gfx::Rect& position_relative_to,
    475                                      BubbleBorder::ArrowLocation arrow_location,
    476                                      FirstRun::BubbleType bubble_type) {
    477   FirstRunBubble* bubble = new FirstRunBubble();
    478   FirstRunBubbleViewBase* view = NULL;
    479 
    480   switch (bubble_type) {
    481     case FirstRun::OEM_BUBBLE:
    482       view = new FirstRunOEMBubbleView(bubble, profile);
    483       break;
    484     case FirstRun::LARGE_BUBBLE:
    485       view = new FirstRunBubbleView(bubble, profile);
    486       break;
    487     case FirstRun::MINIMAL_BUBBLE:
    488       view = new FirstRunMinimalBubbleView(bubble, profile);
    489       break;
    490     default:
    491       NOTREACHED();
    492   }
    493   bubble->set_view(view);
    494   bubble->InitBubble(
    495       parent, position_relative_to, arrow_location, view, bubble);
    496   bubble->GetFocusManager()->AddFocusChangeListener(view);
    497   view->BubbleShown();
    498   return bubble;
    499 }
    500 
    501 FirstRunBubble::FirstRunBubble()
    502     : has_been_activated_(false),
    503       ALLOW_THIS_IN_INITIALIZER_LIST(enable_window_method_factory_(this)),
    504       view_(NULL) {
    505 }
    506 
    507 FirstRunBubble::~FirstRunBubble() {
    508   enable_window_method_factory_.RevokeAll();
    509   GetFocusManager()->RemoveFocusChangeListener(view_);
    510 }
    511 
    512 void FirstRunBubble::EnableParent() {
    513   ::EnableWindow(GetParent(), true);
    514   // The EnableWindow() call above causes the parent to become active, which
    515   // resets the flag set by Bubble's call to DisableInactiveRendering(), so we
    516   // have to call it again before activating the bubble to prevent the parent
    517   // window from rendering inactive.
    518   // TODO(beng): this only works in custom-frame mode, not glass-frame mode.
    519   views::NativeWidget* parent =
    520       views::NativeWidget::GetNativeWidgetForNativeView(GetParent());
    521   if (parent)
    522     parent->GetWidget()->GetWindow()->DisableInactiveRendering();
    523   // Reactivate the FirstRunBubble so it responds to OnActivate messages.
    524   SetWindowPos(GetParent(), 0, 0, 0, 0,
    525                SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_SHOWWINDOW);
    526 }
    527 
    528 void FirstRunBubble::OnActivate(UINT action, BOOL minimized, HWND window) {
    529   // Keep the bubble around for kLingerTime milliseconds, to prevent accidental
    530   // closure.
    531   const int kLingerTime = 3000;
    532 
    533   // We might get re-enabled right before we are closed (sequence is: we get
    534   // deactivated, we call close, before we are actually closed we get
    535   // reactivated). Don't do the disabling of the parent in such cases.
    536   if (action == WA_ACTIVE && !has_been_activated_) {
    537     has_been_activated_ = true;
    538 
    539     ::EnableWindow(GetParent(), false);
    540 
    541     MessageLoop::current()->PostDelayedTask(FROM_HERE,
    542         enable_window_method_factory_.NewRunnableMethod(
    543             &FirstRunBubble::EnableParent),
    544         kLingerTime);
    545     return;
    546   }
    547 
    548   // Keep window from automatically closing until kLingerTime has passed.
    549   if (::IsWindowEnabled(GetParent()))
    550     Bubble::OnActivate(action, minimized, window);
    551 }
    552 
    553 void FirstRunBubble::BubbleClosing(Bubble* bubble, bool closed_by_escape) {
    554   // Make sure our parent window is re-enabled.
    555   if (!IsWindowEnabled(GetParent()))
    556     ::EnableWindow(GetParent(), true);
    557 }
    558