#include "chrome/browser/ui/views/search_view_controller.h"
#include "chrome/browser/instant/instant_controller.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/search_engine_type.h"
#include "chrome/browser/search_engines/template_url.h"
#include "chrome/browser/search_engines/template_url_prepopulate_data.h"
#include "chrome/browser/search_engines/template_url_service.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/search/search_model.h"
#include "chrome/browser/ui/search/search_tab_helper.h"
#include "chrome/browser/ui/search/search_types.h"
#include "chrome/browser/ui/search/search_ui.h"
#include "chrome/browser/ui/search/toolbar_search_animator.h"
#include "chrome/browser/ui/tab_contents/tab_contents.h"
#include "chrome/browser/ui/views/frame/contents_container.h"
#include "chrome/browser/ui/views/location_bar/location_bar_container.h"
#include "chrome/browser/ui/views/toolbar_view.h"
#include "chrome/browser/ui/webui/instant_ui.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/url_constants.h"
#include "content/public/browser/notification_service.h"
#include "grit/theme_resources.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/compositor/layer.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/gfx/canvas.h"
#include "ui/views/controls/image_view.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/layout/layout_manager.h"
#if defined(USE_AURA)
#include "ui/aura/window.h"
#endif
namespace {
void StackViewsLayerAtTop(views::View* view) {
DCHECK(view->layer());
DCHECK(view->layer()->parent());
view->layer()->parent()->StackAtTop(view->layer());
}
void StackWebViewLayerAtTop(views::WebView* view) {
if (!view->web_contents())
return;
ui::Layer* native_view_layer = view->web_contents()->GetNativeView()->layer();
native_view_layer->parent()->StackAtTop(native_view_layer);
}
class SearchContainerView : public views::View {
public:
SearchContainerView(views::View* ntp_container,
views::View* omnibox_popup_parent)
: ntp_container_(ntp_container),
omnibox_popup_parent_(omnibox_popup_parent) {
AddChildView(ntp_container);
AddChildView(omnibox_popup_parent);
}
virtual void Layout() OVERRIDE {
ntp_container_->SetBounds(0, 0, width(), height());
gfx::Size preferred_size(omnibox_popup_parent_->GetPreferredSize());
int old_height = omnibox_popup_parent_->height();
omnibox_popup_parent_->SetBounds(0, 0, width(), preferred_size.height());
int border_y = std::max(old_height, preferred_size.height()) + 1;
SchedulePaintInRect(gfx::Rect(0, 0, width(), border_y));
}
virtual void OnPaintBackground(gfx::Canvas* canvas) OVERRIDE {
canvas->DrawColor(chrome::search::kSearchBackgroundColor);
gfx::Size preferred_size = omnibox_popup_parent_->GetPreferredSize();
canvas->FillRect(gfx::Rect(0, 0, width(), preferred_size.height()),
chrome::search::kSuggestBackgroundColor);
canvas->FillRect(
gfx::Rect(0, preferred_size.height(), width(), 1),
chrome::search::kResultsSeparatorColor);
}
private:
views::View* ntp_container_;
views::View* omnibox_popup_parent_;
DISALLOW_COPY_AND_ASSIGN(SearchContainerView);
};
class NTPViewBackground : public views::Background {
public:
NTPViewBackground() {}
virtual ~NTPViewBackground() {}
virtual void Paint(gfx::Canvas* canvas, views::View* view) const OVERRIDE {
canvas->DrawColor(chrome::search::kNTPBackgroundColor);
int height = view->layer()->bounds().height();
if (height < chrome::search::kSearchResultsHeight)
return;
canvas->FillRect(gfx::Rect(0, height - 1, view->width(), 1),
chrome::search::kResultsSeparatorColor);
}
private:
DISALLOW_COPY_AND_ASSIGN(NTPViewBackground);
};
class NTPViewLayoutManager : public views::LayoutManager {
public:
NTPViewLayoutManager(views::View* logo_view, views::WebView* content_view)
: logo_view_(logo_view),
content_view_(content_view) {
}
virtual ~NTPViewLayoutManager() {}
virtual void Layout(views::View* host) OVERRIDE {
gfx::Size preferred_size = logo_view_->GetPreferredSize();
logo_view_->SetBounds(
(host->width() - preferred_size.width()) / 2,
chrome::search::kLogoYPosition,
preferred_size.width(),
preferred_size.height());
const int kContentTop = logo_view_->bounds().bottom() +
chrome::search::kLogoBottomGap +
chrome::search::kNTPOmniboxHeight +
chrome::search::kOmniboxBottomGap;
content_view_->SetBounds(0,
kContentTop,
host->width(),
host->height() - kContentTop);
StackWebViewLayerAtTop(content_view_);
}
virtual gfx::Size GetPreferredSize(views::View* host) OVERRIDE {
return gfx::Size();
}
private:
views::View* logo_view_;
views::WebView* content_view_;
DISALLOW_COPY_AND_ASSIGN(NTPViewLayoutManager);
};
}
namespace internal {
class OmniboxPopupContainer : public views::View {
public:
explicit OmniboxPopupContainer(SearchViewController* search_view_controller);
virtual ~OmniboxPopupContainer();
bool is_child_visible() { return child_count() && child_at(0)->visible(); }
protected:
virtual void ChildPreferredSizeChanged(views::View* child) OVERRIDE;
private:
SearchViewController* search_view_controller_;
bool child_visible_;
DISALLOW_COPY_AND_ASSIGN(OmniboxPopupContainer);
};
OmniboxPopupContainer::OmniboxPopupContainer(
SearchViewController* search_view_controller)
: search_view_controller_(search_view_controller),
child_visible_(false) {
SetLayoutManager(new views::FillLayout);
}
OmniboxPopupContainer::~OmniboxPopupContainer() {
}
void OmniboxPopupContainer::ChildPreferredSizeChanged(
views::View* child) {
if (parent() && (child->visible() || child_visible_))
parent()->Layout();
if (child_visible_ != child->visible()) {
child_visible_ = child->visible();
search_view_controller_->PopupVisibilityChanged();
}
}
}
SearchViewController::SearchViewController(
content::BrowserContext* browser_context,
ContentsContainer* contents_container,
chrome::search::ToolbarSearchAnimator* toolbar_search_animator,
ToolbarView* toolbar_view)
: browser_context_(browser_context),
contents_container_(contents_container),
toolbar_search_animator_(toolbar_search_animator),
toolbar_view_(toolbar_view),
location_bar_container_(NULL),
state_(STATE_NOT_VISIBLE),
tab_contents_(NULL),
search_container_(NULL),
ntp_container_(NULL),
content_view_(NULL),
omnibox_popup_parent_(NULL) {
omnibox_popup_parent_ = new internal::OmniboxPopupContainer(this);
}
SearchViewController::~SearchViewController() {
if (search_model())
search_model()->RemoveObserver(this);
if (!omnibox_popup_parent_->parent())
delete omnibox_popup_parent_;
}
views::View* SearchViewController::omnibox_popup_parent() {
return omnibox_popup_parent_;
}
void SearchViewController::SetTabContents(TabContents* tab_contents) {
if (tab_contents_ == tab_contents)
return;
if (search_model())
search_model()->RemoveObserver(this);
tab_contents_ = tab_contents;
if (search_model())
search_model()->AddObserver(this);
UpdateState();
}
void SearchViewController::StackAtTop() {
#if defined(USE_AURA)
if (search_container_) {
StackViewsLayerAtTop(search_container_);
StackViewsLayerAtTop(ntp_container_);
StackViewsLayerAtTop(GetLogoView());
StackWebViewLayerAtTop(content_view_);
}
#else
NOTIMPLEMENTED();
#endif
location_bar_container_->StackAtTop();
}
void SearchViewController::InstantReady() {
}
void SearchViewController::ModeChanged(const chrome::search::Mode& old_mode,
const chrome::search::Mode& new_mode) {
if (!(old_mode.mode == chrome::search::Mode::MODE_SEARCH_SUGGESTIONS &&
new_mode.is_default() &&
omnibox_popup_parent_->is_child_visible())) {
UpdateState();
}
}
gfx::Rect SearchViewController::GetNTPOmniboxBounds(views::View* destination) {
if (!is_ntp_state(state_))
return gfx::Rect();
const float kNTPPageWidthRatio = 0.73f;
int omnibox_width = kNTPPageWidthRatio * ntp_container_->bounds().width();
int omnibox_x = (ntp_container_->bounds().width() - omnibox_width) / 2;
gfx::Point omnibox_origin(omnibox_x,
GetLogoView()->bounds().bottom() +
chrome::search::kLogoBottomGap);
views::View::ConvertPointToTarget(ntp_container_, destination,
&omnibox_origin);
return gfx::Rect(omnibox_origin.x(), omnibox_origin.y(),
omnibox_width, chrome::search::kNTPOmniboxHeight);
}
void SearchViewController::OnImplicitAnimationsCompleted() {
DCHECK_EQ(STATE_NTP_ANIMATING, state_);
state_ = STATE_SUGGESTIONS;
ntp_container_->SetVisible(false);
MaybeHideOverlay();
if (omnibox_popup_parent_->is_child_visible())
omnibox_popup_parent_->child_at(0)->Layout();
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_SEARCH_VIEW_CONTROLLER_ANIMATION_FINISHED,
content::Source<SearchViewController>(this),
content::NotificationService::NoDetails());
}
bool SearchViewController::is_ntp_state(State state) {
return state == STATE_NTP || state == STATE_NTP_LOADING;
}
void SearchViewController::UpdateState() {
if (!search_model()) {
DestroyViews();
return;
}
State new_state = STATE_NOT_VISIBLE;
switch (search_model()->mode().mode) {
case chrome::search::Mode::MODE_DEFAULT:
break;
case chrome::search::Mode::MODE_NTP_LOADING:
new_state = STATE_NTP_LOADING;
break;
case chrome::search::Mode::MODE_NTP:
new_state = STATE_NTP;
break;
case chrome::search::Mode::MODE_SEARCH_SUGGESTIONS:
if (search_model()->mode().animate && is_ntp_state(state_))
new_state = STATE_NTP_ANIMATING;
else if (omnibox_popup_parent_->is_child_visible())
new_state = STATE_SUGGESTIONS;
break;
case chrome::search::Mode::MODE_SEARCH_RESULTS:
new_state = STATE_NOT_VISIBLE;
break;
}
SetState(new_state);
}
void SearchViewController::SetState(State state) {
if (state_ == state)
return;
State old_state = state_;
switch (state) {
case STATE_NOT_VISIBLE:
DestroyViews();
break;
case STATE_NTP_LOADING:
case STATE_NTP:
DestroyViews();
CreateViews(state);
break;
case STATE_NTP_ANIMATING:
DCHECK(is_ntp_state(old_state));
StartAnimation();
break;
case STATE_SUGGESTIONS:
DestroyViews();
CreateViews(state);
break;
}
state_ = state;
if (state_ == STATE_NTP_LOADING || state_ == STATE_NTP)
toolbar_view_->LayoutForSearch();
}
void SearchViewController::StartAnimation() {
int factor = InstantUI::GetSlowAnimationScaleFactor();
{
ui::Layer* ntp_layer = ntp_container_->layer();
ui::ScopedLayerAnimationSettings settings(ntp_layer->GetAnimator());
settings.AddObserver(this);
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(180 * factor));
settings.SetTweenType(ui::Tween::EASE_IN_OUT);
gfx::Rect bounds(ntp_layer->bounds());
bounds.set_height(1);
ntp_layer->SetBounds(bounds);
}
{
ui::Layer* logo_layer = GetLogoView()->layer();
ui::ScopedLayerAnimationSettings settings(logo_layer->GetAnimator());
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(135 * factor));
settings.SetTweenType(ui::Tween::EASE_IN_OUT);
gfx::Rect bounds(logo_layer->bounds());
bounds.set_y(bounds.y() - 100);
logo_layer->SetBounds(bounds);
logo_layer->SetOpacity(0.0f);
}
{
ui::Layer* content_layer =
content_view_->web_contents()->GetNativeView()->layer();
ui::ScopedLayerAnimationSettings settings(content_layer->GetAnimator());
settings.SetTransitionDuration(
base::TimeDelta::FromMilliseconds(180 * factor));
settings.SetTweenType(ui::Tween::LINEAR);
gfx::Rect bounds(content_layer->bounds());
bounds.set_y(bounds.y() - 250);
content_layer->SetBounds(bounds);
content_layer->SetOpacity(0.0f);
}
}
void SearchViewController::StopAnimation() {
ntp_container_->layer()->GetAnimator()->StopAnimating();
GetLogoView()->layer()->GetAnimator()->StopAnimating();
if (content_view_->web_contents()) {
content_view_->web_contents()->GetNativeView()->layer()->GetAnimator()->
StopAnimating();
}
}
void SearchViewController::CreateViews(State state) {
DCHECK(!ntp_container_);
ntp_container_ = new views::View;
ntp_container_->set_background(new NTPViewBackground);
ntp_container_->SetPaintToLayer(true);
ntp_container_->layer()->SetMasksToBounds(true);
const TemplateURL* default_provider =
TemplateURLServiceFactory::GetForProfile(
Profile::FromBrowserContext(browser_context_))->
GetDefaultSearchProvider();
if (default_provider &&
InstantUI::ShouldShowSearchProviderLogo(browser_context_) &&
(TemplateURLPrepopulateData::GetEngineType(default_provider->url()) ==
SEARCH_ENGINE_GOOGLE)) {
default_provider_logo_.reset(new views::ImageView());
default_provider_logo_->set_owned_by_client();
default_provider_logo_->SetImage(ui::ResourceBundle::GetSharedInstance().
GetImageSkiaNamed(IDR_GOOGLE_LOGO_LG));
default_provider_logo_->SetPaintToLayer(true);
default_provider_logo_->SetFillsBoundsOpaquely(false);
}
if (!default_provider_logo_.get()) {
default_provider_name_.reset(new views::Label(
default_provider ? default_provider->short_name() : string16()));
default_provider_name_->set_owned_by_client();
default_provider_name_->SetBackgroundColor(0x00000000);
default_provider_name_->set_background(
views::Background::CreateSolidBackground(SK_ColorRED));
default_provider_name_->SetEnabledColor(SK_ColorRED);
default_provider_name_->SetFont(
default_provider_name_->font().DeriveFont(75, gfx::Font::BOLD));
default_provider_name_->SetPaintToLayer(true);
default_provider_name_->SetFillsBoundsOpaquely(false);
}
content_view_ = contents_container_->active();
DCHECK(content_view_);
contents_container_->SetActive(NULL);
views::View* logo_view = GetLogoView();
DCHECK(logo_view);
ntp_container_->SetLayoutManager(
new NTPViewLayoutManager(logo_view, content_view_));
ntp_container_->AddChildView(logo_view);
ntp_container_->AddChildView(content_view_);
search_container_ =
new SearchContainerView(ntp_container_, omnibox_popup_parent_);
search_container_->SetPaintToLayer(true);
search_container_->SetLayoutManager(new views::FillLayout);
search_container_->layer()->SetMasksToBounds(true);
contents_container_->SetOverlay(search_container_);
if (state == STATE_SUGGESTIONS) {
ntp_container_->SetVisible(false);
MaybeHideOverlay();
}
if (state == STATE_NTP_LOADING) {
content_view_->SetVisible(false);
content_view_->web_contents()->WasShown();
} else {
content_view_->SetVisible(true);
}
}
views::View* SearchViewController::GetLogoView() const {
if (default_provider_logo_.get())
return default_provider_logo_.get();
return default_provider_name_.get();
}
void SearchViewController::DestroyViews() {
if (!search_container_)
return;
StopAnimation();
omnibox_popup_parent_->parent()->RemoveChildView(
omnibox_popup_parent_);
ntp_container_->SetLayoutManager(NULL);
ntp_container_->RemoveChildView(content_view_);
if (content_view_->web_contents())
content_view_->web_contents()->GetNativeView()->layer()->SetOpacity(1.0f);
content_view_->SetVisible(true);
contents_container_->SetActive(content_view_);
contents_container_->SetOverlay(NULL);
delete search_container_;
search_container_ = NULL;
ntp_container_ = NULL;
default_provider_logo_.reset();
default_provider_name_.reset();
content_view_ = NULL;
state_ = STATE_NOT_VISIBLE;
}
void SearchViewController::PopupVisibilityChanged() {
if (state_ != STATE_NTP_ANIMATING ||
!omnibox_popup_parent_->is_child_visible()) {
UpdateState();
if (!omnibox_popup_parent_->is_child_visible())
toolbar_search_animator_->OnOmniboxPopupClosed();
}
}
void SearchViewController::MaybeHideOverlay() {
search_container_->SetVisible(
!InstantController::IsInstantEnabled(
Profile::FromBrowserContext(browser_context_)));
}
chrome::search::SearchModel* SearchViewController::search_model() {
return tab_contents_ ? chrome::search::SearchTabHelper::FromWebContents(
tab_contents_->web_contents())->model()
: NULL;
}
content::WebContents* SearchViewController::web_contents() {
return tab_contents_ ? tab_contents_->web_contents() : NULL;
}