// Copyright (c) 2006-2008 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. #include "chrome/browser/views/first_run_bubble.h" #include "app/l10n_util.h" #include "app/resource_bundle.h" #include "base/win_util.h" #include "chrome/browser/browser.h" #include "chrome/browser/browser_list.h" #include "chrome/browser/browser_window.h" #include "chrome/browser/options_window.h" #include "chrome/browser/profile.h" #include "chrome/browser/search_engines/template_url_model.h" #include "grit/chromium_strings.h" #include "grit/generated_resources.h" #include "grit/locale_settings.h" #include "grit/theme_resources.h" #include "views/event.h" #include "views/controls/button/native_button.h" #include "views/controls/button/image_button.h" #include "views/controls/label.h" #include "views/focus/focus_manager.h" #include "views/standard_layout.h" #include "views/window/window.h" namespace { // How much extra padding to put around our content over what the InfoBubble // provides. static const int kBubblePadding = 4; // How much extra padding to put around our content over what the InfoBubble // provides in alternative OEM bubble. static const int kOEMBubblePadding = 4; // Padding between parts of strings on the same line (for instance, // "New!" and "Search from the address bar!" static const int kStringSeparationPadding = 2; // Margin around close button. static const int kMarginRightOfCloseButton = 7; std::wstring GetDefaultSearchEngineName(Profile* profile) { if (!profile) { NOTREACHED(); return std::wstring(); } const TemplateURL* const default_provider = profile->GetTemplateURLModel()->GetDefaultSearchProvider(); if (!default_provider) { // TODO(cpu): bug 1187517. It is possible to have no default provider. // returning an empty string is a stopgap measure for the crash // http://code.google.com/p/chromium/issues/detail?id=2573 return std::wstring(); } return default_provider->short_name(); } } // namespace // Base class for implementations of the client view which appears inside the // first run bubble. It is a dialog-ish view, but is not a true dialog. class FirstRunBubbleViewBase : public views::View, public views::ButtonListener, public views::FocusChangeListener { public: // Called by FirstRunBubble::Show to request focus for the proper button // in the FirstRunBubbleView when it is shown. virtual void BubbleShown() = 0; }; class FirstRunBubbleView : public FirstRunBubbleViewBase { public: FirstRunBubbleView(FirstRunBubble* bubble_window, Profile* profile) : bubble_window_(bubble_window), label1_(NULL), label2_(NULL), label3_(NULL), keep_button_(NULL), change_button_(NULL) { gfx::Font& font = ResourceBundle::GetSharedInstance().GetFont(ResourceBundle::MediumFont); label1_ = new views::Label(l10n_util::GetString(IDS_FR_BUBBLE_TITLE)); label1_->SetFont(font.DeriveFont(3, gfx::Font::BOLD)); label1_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); AddChildView(label1_); gfx::Size ps = GetPreferredSize(); label2_ = new views::Label(l10n_util::GetString(IDS_FR_BUBBLE_SUBTEXT)); label2_->SetMultiLine(true); label2_->SetFont(font); label2_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); label2_->SizeToFit(ps.width() - kBubblePadding * 2); AddChildView(label2_); std::wstring question_str = l10n_util::GetStringF(IDS_FR_BUBBLE_QUESTION, GetDefaultSearchEngineName(profile)); label3_ = new views::Label(question_str); label3_->SetMultiLine(true); label3_->SetFont(font); label3_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); label3_->SizeToFit(ps.width() - kBubblePadding * 2); AddChildView(label3_); std::wstring keep_str = l10n_util::GetStringF(IDS_FR_BUBBLE_OK, GetDefaultSearchEngineName(profile)); keep_button_ = new views::NativeButton(this, keep_str); keep_button_->SetIsDefault(true); AddChildView(keep_button_); std::wstring change_str = l10n_util::GetString(IDS_FR_BUBBLE_CHANGE); change_button_ = new views::NativeButton(this, change_str); AddChildView(change_button_); } void BubbleShown() { keep_button_->RequestFocus(); } virtual void ButtonPressed(views::Button* sender) { bubble_window_->Close(); if (change_button_ == sender) { Browser* browser = BrowserList::GetLastActive(); if (browser) { ShowOptionsWindow(OPTIONS_PAGE_GENERAL, OPTIONS_GROUP_DEFAULT_SEARCH, browser->profile()); } } } virtual void Layout() { gfx::Size canvas = GetPreferredSize(); // The multiline business that follows is dirty hacks to get around // bug 1325257. label1_->SetMultiLine(false); gfx::Size pref_size = label1_->GetPreferredSize(); label1_->SetMultiLine(true); label1_->SizeToFit(canvas.width() - kBubblePadding * 2); label1_->SetBounds(kBubblePadding, kBubblePadding, canvas.width() - kBubblePadding * 2, pref_size.height()); int next_v_space = label1_->y() + pref_size.height() + kRelatedControlSmallVerticalSpacing; pref_size = label2_->GetPreferredSize(); label2_->SetBounds(kBubblePadding, next_v_space, canvas.width() - kBubblePadding * 2, pref_size.height()); next_v_space = label2_->y() + label2_->height() + kPanelSubVerticalSpacing; pref_size = label3_->GetPreferredSize(); label3_->SetBounds(kBubblePadding, next_v_space, canvas.width() - kBubblePadding * 2, pref_size.height()); pref_size = change_button_->GetPreferredSize(); change_button_->SetBounds( canvas.width() - pref_size.width() - kBubblePadding, canvas.height() - pref_size.height() - kButtonVEdgeMargin, pref_size.width(), pref_size.height()); pref_size = keep_button_->GetPreferredSize(); keep_button_->SetBounds(change_button_->x() - pref_size.width() - kRelatedButtonHSpacing, change_button_->y(), pref_size.width(), pref_size.height()); } virtual gfx::Size GetPreferredSize() { return gfx::Size(views::Window::GetLocalizedContentsSize( IDS_FIRSTRUNBUBBLE_DIALOG_WIDTH_CHARS, IDS_FIRSTRUNBUBBLE_DIALOG_HEIGHT_LINES)); } virtual void FocusWillChange(View* focused_before, View* focused_now) { if (focused_before && focused_before->GetClassName() == views::NativeButton::kViewClassName) { views::NativeButton* before = static_cast(focused_before); before->SetIsDefault(false); } if (focused_now && focused_now->GetClassName() == views::NativeButton::kViewClassName) { views::NativeButton* after = static_cast(focused_now); after->SetIsDefault(true); } } private: FirstRunBubble* bubble_window_; views::Label* label1_; views::Label* label2_; views::Label* label3_; views::NativeButton* change_button_; views::NativeButton* keep_button_; DISALLOW_COPY_AND_ASSIGN(FirstRunBubbleView); }; class FirstRunOEMBubbleView : public FirstRunBubbleViewBase { public: FirstRunOEMBubbleView(FirstRunBubble* bubble_window, Profile* profile) : bubble_window_(bubble_window), label1_(NULL), label2_(NULL), label3_(NULL), close_button_(NULL) { ResourceBundle& rb = ResourceBundle::GetSharedInstance(); gfx::Font& font = rb.GetFont(ResourceBundle::MediumFont); label1_ = new views::Label( l10n_util::GetString(IDS_FR_OEM_BUBBLE_TITLE_1)); label1_->SetFont(font.DeriveFont(3, gfx::Font::BOLD)); label1_->SetColor(SK_ColorRED); label1_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); AddChildView(label1_); label2_ = new views::Label( l10n_util::GetString(IDS_FR_OEM_BUBBLE_TITLE_2)); label2_->SetFont(font.DeriveFont(3, gfx::Font::BOLD)); label2_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); AddChildView(label2_); gfx::Size ps = GetPreferredSize(); label3_ = new views::Label( l10n_util::GetString(IDS_FR_OEM_BUBBLE_SUBTEXT)); label3_->SetMultiLine(true); label3_->SetFont(font); label3_->SetHorizontalAlignment(views::Label::ALIGN_LEFT); label3_->SizeToFit(ps.width() - kOEMBubblePadding * 2); AddChildView(label3_); close_button_ = new views::ImageButton(this); close_button_->SetImage(views::CustomButton::BS_NORMAL, rb.GetBitmapNamed(IDR_CLOSE_BAR)); close_button_->SetImage(views::CustomButton::BS_HOT, rb.GetBitmapNamed(IDR_CLOSE_BAR_H)); close_button_->SetImage(views::CustomButton::BS_PUSHED, rb.GetBitmapNamed(IDR_CLOSE_BAR_P)); AddChildView(close_button_); } void BubbleShown() { this->RequestFocus(); // No button in oem_bubble to request focus. } virtual void ButtonPressed(views::Button* sender) { bubble_window_->Close(); } virtual void Layout() { gfx::Size canvas = GetPreferredSize(); // First, draw the close button on the far right. gfx::Size sz = close_button_->GetPreferredSize(); close_button_->SetBounds(canvas.width() - sz.width() - kMarginRightOfCloseButton, kOEMBubblePadding, sz.width(), sz.height()); gfx::Size pref_size = label1_->GetPreferredSize(); label1_->SetBounds(kOEMBubblePadding, kOEMBubblePadding, pref_size.width() + kOEMBubblePadding * 2, pref_size.height()); pref_size = label2_->GetPreferredSize(); label2_->SetBounds(kOEMBubblePadding * 2 + label1_-> GetPreferredSize().width(), kOEMBubblePadding, canvas.width() - kOEMBubblePadding * 2, pref_size.height()); int next_v_space = label1_->y() + pref_size.height() + kRelatedControlSmallVerticalSpacing; pref_size = label3_->GetPreferredSize(); label3_->SetBounds(kOEMBubblePadding, next_v_space, canvas.width() - kOEMBubblePadding * 2, pref_size.height()); } virtual gfx::Size GetPreferredSize() { return gfx::Size(views::Window::GetLocalizedContentsSize( IDS_FIRSTRUNOEMBUBBLE_DIALOG_WIDTH_CHARS, IDS_FIRSTRUNOEMBUBBLE_DIALOG_HEIGHT_LINES)); } virtual void FocusWillChange(View* focused_before, View* focused_now) { // No buttons in oem_bubble to register focus changes. } private: FirstRunBubble* bubble_window_; views::Label* label1_; views::Label* label2_; views::Label* label3_; views::ImageButton* close_button_; DISALLOW_COPY_AND_ASSIGN(FirstRunOEMBubbleView); }; // Keep the bubble around for kLingerTime milliseconds, to prevent accidental // closure. static const int kLingerTime = 1000; void FirstRunBubble::OnActivate(UINT action, BOOL minimized, HWND window) { // We might get re-enabled right before we are closed (sequence is: we get // deactivated, we call close, before we are actually closed we get // reactivated). Don't do the disabling of the parent in such cases. if (action == WA_ACTIVE && !has_been_activated_) { has_been_activated_ = true; ::EnableWindow(GetParent(), false); MessageLoop::current()->PostDelayedTask(FROM_HERE, enable_window_method_factory_.NewRunnableMethod( &FirstRunBubble::EnableParent), kLingerTime); } InfoBubble::OnActivate(action, minimized, window); } void FirstRunBubble::InfoBubbleClosing(InfoBubble* info_bubble, bool closed_by_escape) { // Make sure our parent window is re-enabled. if (!IsWindowEnabled(GetParent())) ::EnableWindow(GetParent(), true); enable_window_method_factory_.RevokeAll(); views::FocusManager* focus_manager = views::FocusManager::GetFocusManager(GetNativeView()); focus_manager->RemoveFocusChangeListener(view_); } // static FirstRunBubble* FirstRunBubble::Show(Profile* profile, views::Window* parent, const gfx::Rect& position_relative_to, bool use_OEM_bubble) { FirstRunBubble* window = new FirstRunBubble(); FirstRunBubbleViewBase* view = NULL; if (use_OEM_bubble) view = new FirstRunOEMBubbleView(window, profile); else view = new FirstRunBubbleView(window, profile); window->SetDelegate(window); window->set_view(view); window->Init(parent, position_relative_to, view); window->ShowWindow(SW_SHOW); views::FocusManager* focus_manager = views::FocusManager::GetFocusManager(window->GetNativeView()); focus_manager->AddFocusChangeListener(view); view->BubbleShown(); return window; } void FirstRunBubble::EnableParent() { ::EnableWindow(GetParent(), true); }