blob: 9ff65ceb49b0861606395d9eeb8dd582980e6361 [file] [log] [blame]
// 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/tab_contents/native_ui_contents.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/history_tab_ui.h"
#include "chrome/browser/tab_contents/navigation_entry.h"
#include "chrome/browser/views/download_tab_view.h"
#include "chrome/common/drag_drop_types.h"
#include "chrome/common/gfx/chrome_canvas.h"
#include "chrome/common/gfx/chrome_font.h"
#include "chrome/common/l10n_util.h"
#include "chrome/common/os_exchange_data.h"
#include "chrome/common/resource_bundle.h"
#include "chrome/views/background.h"
#include "chrome/views/checkbox.h"
#include "chrome/views/grid_layout.h"
#include "chrome/views/image_view.h"
#include "chrome/views/root_view.h"
#include "chrome/views/scroll_view.h"
#include "chrome/views/throbber.h"
#include "chrome/views/widget_win.h"
#include "generated_resources.h"
using views::ColumnSet;
using views::GridLayout;
//static
bool NativeUIContents::g_ui_factories_initialized = false;
// The URL scheme currently used.
static const char kNativeUIContentsScheme[] = "chrome-nativeui";
// Unique page id generator.
static int g_next_page_id = 0;
// The x-position of the title.
static const int kDestinationTitleOffset = 38;
// The x-position of the search field.
static const int kDestinationSearchOffset = 128;
// The width of the search field.
static const int kDestinationSearchWidth = 360;
// Padding between columns
static const int kDestinationSmallerMargin = 8;
// The background color.
static const SkColor kBackground = SkColorSetRGB(255, 255, 255);
// The color of the bottom margin.
static const SkColor kBottomMarginColor = SkColorSetRGB(246, 249, 255);
// The height of the bottom margin.
static const int kBottomMargin = 5;
// The Chrome product logo.
static const SkBitmap* kProductLogo = NULL;
// Padding around the product logo.
static const int kProductLogoPadding = 8;
namespace {
// NativeRootView --------------------------------------------------------------
// NativeRootView is a trivial RootView subclass that allows URL drops and
// forwards them to the NavigationController to open.
class NativeRootView : public views::RootView {
public:
explicit NativeRootView(NativeUIContents* host)
: RootView(host),
host_(host) { }
virtual ~NativeRootView() { }
virtual bool CanDrop(const OSExchangeData& data) {
return data.HasURL();
}
virtual int OnDragUpdated(const views::DropTargetEvent& event) {
if (event.GetSourceOperations() & DragDropTypes::DRAG_COPY)
return DragDropTypes::DRAG_COPY;
if (event.GetSourceOperations() & DragDropTypes::DRAG_LINK)
return DragDropTypes::DRAG_LINK;
return DragDropTypes::DRAG_NONE;
}
virtual int OnPerformDrop(const views::DropTargetEvent& event) {
GURL url;
std::wstring title;
if (!event.GetData().GetURLAndTitle(&url, &title) || !url.is_valid())
return DragDropTypes::DRAG_NONE;
host_->controller()->LoadURL(url, GURL(), PageTransition::GENERATED);
return OnDragUpdated(event);
}
private:
NativeUIContents* host_;
DISALLOW_EVIL_CONSTRUCTORS(NativeRootView);
};
} // namespace
// Returns the end of the scheme and end of the host. This is temporary until
// bug 772411 is fixed.
static void GetSchemeAndHostEnd(const GURL& url,
size_t* scheme_end,
size_t* host_end) {
const std::string spec = url.spec();
*scheme_end = spec.find("//");
DCHECK(*scheme_end != std::string::npos);
*host_end = spec.find('/', *scheme_end + 2);
if (*host_end == std::string::npos)
*host_end = spec.size();
}
NativeUIContents::NativeUIContents(Profile* profile)
: TabContents(TAB_CONTENTS_NATIVE_UI),
is_visible_(false),
current_ui_(NULL),
current_view_(NULL),
state_(new PageState()) {
if (!g_ui_factories_initialized) {
InitializeNativeUIFactories();
g_ui_factories_initialized = true;
}
}
NativeUIContents::~NativeUIContents() {
if (current_ui_) {
views::RootView* root_view = GetRootView();
current_ui_->WillBecomeInvisible(this);
root_view->RemoveChildView(current_view_);
current_ui_ = NULL;
current_view_ = NULL;
}
STLDeleteContainerPairSecondPointers(path_to_native_uis_.begin(),
path_to_native_uis_.end());
}
void NativeUIContents::CreateView() {
set_delete_on_destroy(false);
WidgetWin::Init(GetDesktopWindow(), gfx::Rect(), false);
}
LRESULT NativeUIContents::OnCreate(LPCREATESTRUCT create_struct) {
// Set the view container initial size.
CRect tmp;
::GetWindowRect(GetHWND(), &tmp);
tmp.right = tmp.Width();
tmp.bottom = tmp.Height();
tmp.left = tmp.top = 0;
// Install the focus manager so we get notified of Tab key events.
views::FocusManager::InstallFocusSubclass(GetHWND(), NULL);
GetRootView()->set_background(new NativeUIBackground);
return 0;
}
void NativeUIContents::OnDestroy() {
views::FocusManager::UninstallFocusSubclass(GetHWND());
}
void NativeUIContents::OnSize(UINT size_command, const CSize& new_size) {
Layout();
::RedrawWindow(GetHWND(), NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN);
}
void NativeUIContents::OnWindowPosChanged(WINDOWPOS* position) {
// NOTE: this may be invoked even when the visbility didn't change, in which
// case hiding and showing are both false.
const bool hiding = (position->flags & SWP_HIDEWINDOW) == SWP_HIDEWINDOW;
const bool showing = (position->flags & SWP_SHOWWINDOW) == SWP_SHOWWINDOW;
if (hiding || showing) {
if (is_visible_ != showing) {
is_visible_ = showing;
if (current_ui_) {
if (is_visible_)
current_ui_->WillBecomeVisible(this);
else
current_ui_->WillBecomeInvisible(this);
}
}
}
ChangeSize(0, CSize(position->cx, position->cy));
SetMsgHandled(FALSE);
}
void NativeUIContents::GetContainerBounds(gfx::Rect* out) const {
GetBounds(out, false);
}
void NativeUIContents::SetPageState(PageState* page_state) {
if (!page_state)
page_state = new PageState();
state_.reset(page_state);
NavigationController* ctrl = controller();
if (ctrl) {
int ne_index = ctrl->GetLastCommittedEntryIndex();
NavigationEntry* ne = ctrl->GetEntryAtIndex(ne_index);
if (ne) {
// NavigationEntry is null if we're being restored.
DCHECK(ne);
std::string rep;
state_->GetByteRepresentation(&rep);
ne->set_content_state(rep);
ctrl->NotifyEntryChanged(ne, ne_index);
}
}
}
bool NativeUIContents::NavigateToPendingEntry(bool reload) {
views::RootView* root_view = GetRootView();
DCHECK(root_view);
if (current_ui_) {
current_ui_->WillBecomeInvisible(this);
root_view->RemoveChildView(current_view_);
current_ui_ = NULL;
current_view_ = NULL;
}
NavigationEntry* pending_entry = controller()->GetPendingEntry();
NativeUI* new_ui = GetNativeUIForURL(pending_entry->url());
if (new_ui) {
current_ui_ = new_ui;
is_visible_ = true;
current_ui_->WillBecomeVisible(this);
current_view_ = new_ui->GetView();
root_view->AddChildView(current_view_);
std::string s = pending_entry->content_state();
if (s.empty())
state_->InitWithURL(pending_entry->url());
else
state_->InitWithBytes(s);
current_ui_->Navigate(*state_);
Layout();
}
// Commit the new load in the navigation controller. If the ID of the
// NavigationEntry we were given was -1, that means this is a new load, so
// we have to generate a new ID.
controller()->CommitPendingEntry();
// Populate the committed entry.
NavigationEntry* committed_entry = controller()->GetLastCommittedEntry();
committed_entry->set_title(GetDefaultTitle());
committed_entry->favicon().set_bitmap(GetFavIcon());
committed_entry->favicon().set_is_valid(true);
if (new_ui) {
// Strip out the query params, they should have moved to state.
// TODO(sky): use GURL methods for replacements once bug is fixed.
size_t scheme_end, host_end;
GetSchemeAndHostEnd(committed_entry->url(), &scheme_end, &host_end);
committed_entry->set_url(
GURL(committed_entry->url().spec().substr(0, host_end)));
}
std::string content_state;
state_->GetByteRepresentation(&content_state);
committed_entry->set_content_state(content_state);
// Broadcast the fact that we just updated all that crap.
controller()->NotifyEntryChanged(
committed_entry,
controller()->GetIndexOfEntry(committed_entry));
return true;
}
void NativeUIContents::Layout() {
if (current_view_) {
views::RootView* root_view = GetRootView();
current_view_->SetBounds(0, 0, root_view->width(),
root_view->height());
current_view_->Layout();
}
}
const std::wstring NativeUIContents::GetDefaultTitle() const {
if (current_ui_)
return current_ui_->GetTitle();
else
return std::wstring();
}
SkBitmap NativeUIContents::GetFavIcon() const {
int icon_id;
if (current_ui_)
icon_id = current_ui_->GetFavIconID();
else
icon_id = IDR_DEFAULT_FAVICON;
return *ResourceBundle::GetSharedInstance().GetBitmapNamed(icon_id);
}
void NativeUIContents::DidBecomeSelected() {
TabContents::DidBecomeSelected();
Layout();
}
void NativeUIContents::SetInitialFocus() {
if (!current_ui_ || !current_ui_->SetInitialFocus()) {
int tab_index;
Browser* browser = Browser::GetBrowserForController(
this->controller(), &tab_index);
if (browser)
browser->FocusLocationBar();
else
TabContents::SetInitialFocus(); // Will set focus to our HWND.
}
}
void NativeUIContents::SetIsLoading(bool is_loading,
LoadNotificationDetails* details) {
TabContents::SetIsLoading(is_loading, details);
}
// FocusTraversable Implementation
views::View* NativeUIContents::FindNextFocusableView(
views::View* starting_view, bool reverse,
views::FocusTraversable::Direction direction, bool dont_loop,
views::FocusTraversable** focus_traversable,
views::View** focus_traversable_view) {
return GetRootView()->FindNextFocusableView(
starting_view, reverse, direction, dont_loop,
focus_traversable, focus_traversable_view);
}
//static
std::string NativeUIContents::GetScheme() {
return kNativeUIContentsScheme;
}
//static
void NativeUIContents::InitializeNativeUIFactories() {
RegisterNativeUIFactory(DownloadTabUI::GetURL(),
DownloadTabUI::GetNativeUIFactory());
RegisterNativeUIFactory(HistoryTabUI::GetURL(),
HistoryTabUI::GetNativeUIFactory());
}
// static
std::string NativeUIContents::GetFactoryKey(const GURL& url) {
size_t scheme_end;
size_t host_end;
GetSchemeAndHostEnd(url, &scheme_end, &host_end);
return url.spec().substr(scheme_end + 2, host_end - scheme_end - 2);
}
typedef std::map<std::string, NativeUIFactory*> PathToFactoryMap;
static PathToFactoryMap* g_path_to_factory = NULL;
//static
void NativeUIContents::RegisterNativeUIFactory(const GURL& url,
NativeUIFactory* factory) {
const std::string key = GetFactoryKey(url);
if (!g_path_to_factory)
g_path_to_factory = new PathToFactoryMap;
PathToFactoryMap::iterator i = g_path_to_factory->find(key);
if (i != g_path_to_factory->end()) {
delete i->second;
g_path_to_factory->erase(i);
}
(*g_path_to_factory)[key] = factory;
}
views::RootView* NativeUIContents::CreateRootView() {
return new NativeRootView(this);
}
//static
NativeUI* NativeUIContents::InstantiateNativeUIForURL(
const GURL& url, NativeUIContents* contents) {
if (!g_path_to_factory)
return NULL;
const std::string key = GetFactoryKey(url);
NativeUIFactory* factory = (*g_path_to_factory)[key];
if (factory)
return factory->CreateNativeUIForURL(url, contents);
else
return NULL;
}
NativeUI* NativeUIContents::GetNativeUIForURL(const GURL& url) {
const std::string key = GetFactoryKey(url);
PathToUI::iterator i = path_to_native_uis_.find(key);
if (i != path_to_native_uis_.end())
return i->second;
NativeUI* ui = InstantiateNativeUIForURL(url, this);
if (ui)
path_to_native_uis_[key] = ui;
return ui;
}
////////////////////////////////////////////////////////////////////////////////
//
// Standard NativeUI background implementation.
//
////////////////////////////////////////////////////////////////////////////////
NativeUIBackground::NativeUIBackground() {
}
NativeUIBackground::~NativeUIBackground() {
}
void NativeUIBackground::Paint(ChromeCanvas* canvas,
views::View* view) const {
static const SkColor kBackground = SkColorSetRGB(255, 255, 255);
canvas->FillRectInt(kBackground, 0, 0, view->width(), view->height());
}
/////////////////////////////////////////////////////////////////////////////
//
// SearchableUIBackground
// A Background subclass to be used with SearchableUIContainer objects.
// Paint() is overridden to do nothing here; the background of the bar is
// painted in SearchableUIContainer::Paint. This class is necessary
// only for native controls to be able to get query the background
// brush.
class SearchableUIBackground : public views::Background {
public:
explicit SearchableUIBackground(SkColor native_control_color) {
SetNativeControlColor(native_control_color);
}
virtual ~SearchableUIBackground() {};
// Empty implementation.
// The actual painting of the bar happens in SearchableUIContainer::Paint.
virtual void Paint(ChromeCanvas* canvas, views::View* view) const { }
private:
DISALLOW_EVIL_CONSTRUCTORS(SearchableUIBackground);
};
/////////////////////////////////////////////////////////////////////////////
//
// SearchableUIContainer implementation.
//
/////////////////////////////////////////////////////////////////////////////
SearchableUIContainer::SearchableUIContainer(
SearchableUIContainer::Delegate* delegate)
: delegate_(delegate),
search_field_(NULL),
title_link_(NULL),
title_image_(NULL),
scroll_view_(NULL) {
title_link_ = new views::Link;
ResourceBundle& resource_bundle = ResourceBundle::GetSharedInstance();
ChromeFont title_font(resource_bundle
.GetFont(ResourceBundle::WebFont).DeriveFont(2));
title_link_->SetFont(title_font);
title_link_->SetHorizontalAlignment(views::Label::ALIGN_LEFT);
title_link_->SetController(this);
title_image_ = new views::ImageView();
title_image_->SetVisible(false);
// Get the product logo
if (!kProductLogo) {
kProductLogo = resource_bundle.GetBitmapNamed(IDR_PRODUCT_LOGO);
}
product_logo_ = new views::ImageView();
product_logo_->SetVisible(true);
product_logo_->SetImage(*kProductLogo);
AddChildView(product_logo_);
search_field_ = new views::TextField;
search_field_->SetFont(ResourceBundle::GetSharedInstance().GetFont(
ResourceBundle::WebFont));
search_field_->SetController(this);
scroll_view_ = new views::ScrollView;
scroll_view_->set_background(
views::Background::CreateSolidBackground(kBackground));
// Set background class so that native controls can get a color.
set_background(new SearchableUIBackground(kBackground));
throbber_ = new views::SmoothedThrobber(50);
GridLayout* layout = new GridLayout(this);
// View owns the LayoutManager and will delete it along with all the columns
// we create here.
SetLayoutManager(layout);
search_button_ =
new views::NativeButton(std::wstring());
search_button_->SetFont(resource_bundle.GetFont(ResourceBundle::WebFont));
search_button_->SetListener(this);
// Set a background color for the search button. If SearchableUIContainer
// provided a background, then the search button could inherit that instead.
search_button_->set_background(new SearchableUIBackground(kBackground));
// For the first row (icon, title/text field, search button and throbber).
ColumnSet* column_set = layout->AddColumnSet(0);
column_set->AddPaddingColumn(0, kDestinationTitleOffset);
// Add the icon column.
column_set->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
GridLayout::USE_PREF,
kDestinationSearchOffset - kDestinationTitleOffset -
kDestinationSmallerMargin,
kDestinationSearchOffset - kDestinationTitleOffset -
kDestinationSmallerMargin);
column_set->AddPaddingColumn(0, kDestinationSmallerMargin);
// Add the title/search field column.
column_set->AddColumn(GridLayout::FILL, GridLayout::CENTER, 0,
GridLayout::USE_PREF, kDestinationSearchWidth,
kDestinationSearchWidth);
column_set->AddPaddingColumn(0, kDestinationSmallerMargin);
// Add the search button column.
column_set->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0,
GridLayout::USE_PREF, 0, 0);
column_set->AddPaddingColumn(0, kDestinationSmallerMargin);
// Add the throbber column.
column_set->AddColumn(GridLayout::CENTER, GridLayout::CENTER, 0,
GridLayout::USE_PREF, 0, 0);
// For the scroll view.
column_set = layout->AddColumnSet(1);
column_set->AddPaddingColumn(0, 1);
column_set->AddColumn(GridLayout::FILL, GridLayout::FILL, 1,
GridLayout::USE_PREF, 0, 0);
layout->AddPaddingRow(0, kDestinationSmallerMargin);
layout->StartRow(0, 0);
layout->AddView(title_image_, 1, 2);
layout->AddView(title_link_);
layout->StartRow(0, 0);
layout->SkipColumns(1);
layout->AddView(search_field_);
layout->AddView(search_button_);
layout->AddView(throbber_);
layout->AddPaddingRow(0, kDestinationSmallerMargin);
layout->StartRow(1, 1);
layout->AddView(scroll_view_);
}
SearchableUIContainer::~SearchableUIContainer() {
}
void SearchableUIContainer::SetContents(views::View* contents) {
// The column view will resize to accomodate long titles.
title_link_->SetText(delegate_->GetTitle());
int section_icon_id = delegate_->GetSectionIconID();
if (section_icon_id != 0) {
title_image_->SetImage(*ResourceBundle::GetSharedInstance().
GetBitmapNamed(section_icon_id));
title_image_->SetVisible(true);
}
search_button_->SetLabel(delegate_->GetSearchButtonText());
scroll_view_->SetContents(contents);
}
views::View* SearchableUIContainer::GetContents() {
return scroll_view_->GetContents();
}
void SearchableUIContainer::Layout() {
View::Layout();
gfx::Size search_button_size = search_button_->GetPreferredSize();
gfx::Size product_logo_size = product_logo_->GetPreferredSize();
int field_width = kDestinationSearchOffset +
kDestinationSearchWidth +
kDestinationSmallerMargin +
static_cast<int>(search_button_size.width()) +
kDestinationSmallerMargin;
product_logo_->SetBounds(std::max(width() - kProductLogo->width() -
kProductLogoPadding,
field_width),
kProductLogoPadding,
product_logo_size.width(),
product_logo_size.height());
}
void SearchableUIContainer::Paint(ChromeCanvas* canvas) {
SkColor top_color(kBackground);
canvas->FillRectInt(top_color, 0, 0,
width(), scroll_view_->y());
canvas->FillRectInt(kBottomMarginColor, 0, scroll_view_->y() -
kBottomMargin, width(), kBottomMargin);
canvas->FillRectInt(SkColorSetRGB(196, 196, 196),
0, scroll_view_->y() - 1, width(), 1);
}
views::TextField* SearchableUIContainer::GetSearchField() const {
return search_field_;
}
views::ScrollView* SearchableUIContainer::GetScrollView() const {
return scroll_view_;
}
void SearchableUIContainer::SetSearchEnabled(bool enabled) {
search_field_->SetReadOnly(!enabled);
search_button_->SetEnabled(enabled);
}
void SearchableUIContainer::StartThrobber() {
throbber_->Start();
}
void SearchableUIContainer::StopThrobber() {
throbber_->Stop();
}
void SearchableUIContainer::ButtonPressed(views::NativeButton* sender) {
DoSearch();
}
void SearchableUIContainer::LinkActivated(views::Link *link,
int event_flags) {
if (link == title_link_) {
search_field_->SetText(std::wstring());
DoSearch();
}
}
void SearchableUIContainer::HandleKeystroke(views::TextField* sender,
UINT message,
TCHAR key,
UINT repeat_count,
UINT flags) {
if (key == VK_RETURN)
DoSearch();
}
void SearchableUIContainer::DoSearch() {
if (delegate_)
delegate_->DoSearch(search_field_->GetText());
scroll_view_->ScrollToPosition(scroll_view_->vertical_scroll_bar(), 0);
}