| // 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); |
| } |
| |