// Copyright (c) 2011 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/tab_contents_view_gtk.h" #include <gdk/gdk.h> #include <gdk/gdkkeysyms.h> #include <gtk/gtk.h> #include <algorithm> #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "build/build_config.h" #include "chrome/browser/download/download_shelf.h" #include "chrome/browser/renderer_host/render_widget_host_view_gtk.h" #include "chrome/browser/tab_contents/render_view_context_menu_gtk.h" #include "chrome/browser/tab_contents/web_drag_dest_gtk.h" #include "chrome/browser/ui/gtk/browser_window_gtk.h" #include "chrome/browser/ui/gtk/constrained_window_gtk.h" #include "chrome/browser/ui/gtk/gtk_expanded_container.h" #include "chrome/browser/ui/gtk/gtk_floating_container.h" #include "chrome/browser/ui/gtk/gtk_util.h" #include "chrome/browser/ui/gtk/sad_tab_gtk.h" #include "chrome/browser/ui/gtk/tab_contents_drag_source.h" #include "content/browser/renderer_host/render_process_host.h" #include "content/browser/renderer_host/render_view_host.h" #include "content/browser/renderer_host/render_view_host_factory.h" #include "content/browser/tab_contents/interstitial_page.h" #include "content/browser/tab_contents/tab_contents.h" #include "content/browser/tab_contents/tab_contents_delegate.h" #include "content/common/notification_source.h" #include "content/common/notification_type.h" #include "ui/gfx/point.h" #include "ui/gfx/rect.h" #include "ui/gfx/size.h" #include "webkit/glue/webdropdata.h" using WebKit::WebDragOperation; using WebKit::WebDragOperationsMask; namespace { // Called when the mouse leaves the widget. We notify our delegate. gboolean OnLeaveNotify(GtkWidget* widget, GdkEventCrossing* event, TabContents* tab_contents) { if (tab_contents->delegate()) tab_contents->delegate()->ContentsMouseEvent( tab_contents, gfx::Point(event->x_root, event->y_root), false); return FALSE; } // Called when the mouse moves within the widget. We notify our delegate. gboolean OnMouseMove(GtkWidget* widget, GdkEventMotion* event, TabContents* tab_contents) { if (tab_contents->delegate()) tab_contents->delegate()->ContentsMouseEvent( tab_contents, gfx::Point(event->x_root, event->y_root), true); return FALSE; } // See tab_contents_view_views.cc for discussion of mouse scroll zooming. gboolean OnMouseScroll(GtkWidget* widget, GdkEventScroll* event, TabContents* tab_contents) { if ((event->state & gtk_accelerator_get_default_mod_mask()) == GDK_CONTROL_MASK) { if (event->direction == GDK_SCROLL_DOWN) { tab_contents->delegate()->ContentsZoomChange(false); return TRUE; } else if (event->direction == GDK_SCROLL_UP) { tab_contents->delegate()->ContentsZoomChange(true); return TRUE; } } return FALSE; } } // namespace // static TabContentsView* TabContentsView::Create(TabContents* tab_contents) { return new TabContentsViewGtk(tab_contents); } TabContentsViewGtk::TabContentsViewGtk(TabContents* tab_contents) : TabContentsView(tab_contents), floating_(gtk_floating_container_new()), expanded_(gtk_expanded_container_new()), constrained_window_(NULL) { gtk_widget_set_name(expanded_, "chrome-tab-contents-view"); g_signal_connect(expanded_, "size-allocate", G_CALLBACK(OnSizeAllocateThunk), this); g_signal_connect(expanded_, "child-size-request", G_CALLBACK(OnChildSizeRequestThunk), this); g_signal_connect(floating_.get(), "set-floating-position", G_CALLBACK(OnSetFloatingPositionThunk), this); gtk_container_add(GTK_CONTAINER(floating_.get()), expanded_); gtk_widget_show(expanded_); gtk_widget_show(floating_.get()); registrar_.Add(this, NotificationType::TAB_CONTENTS_CONNECTED, Source<TabContents>(tab_contents)); drag_source_.reset(new TabContentsDragSource(this)); } TabContentsViewGtk::~TabContentsViewGtk() { floating_.Destroy(); } void TabContentsViewGtk::AttachConstrainedWindow( ConstrainedWindowGtk* constrained_window) { DCHECK(constrained_window_ == NULL); constrained_window_ = constrained_window; gtk_floating_container_add_floating(GTK_FLOATING_CONTAINER(floating_.get()), constrained_window->widget()); } void TabContentsViewGtk::RemoveConstrainedWindow( ConstrainedWindowGtk* constrained_window) { DCHECK(constrained_window == constrained_window_); constrained_window_ = NULL; gtk_container_remove(GTK_CONTAINER(floating_.get()), constrained_window->widget()); } void TabContentsViewGtk::CreateView(const gfx::Size& initial_size) { requested_size_ = initial_size; } RenderWidgetHostView* TabContentsViewGtk::CreateViewForWidget( RenderWidgetHost* render_widget_host) { if (render_widget_host->view()) { // During testing, the view will already be set up in most cases to the // test view, so we don't want to clobber it with a real one. To verify that // this actually is happening (and somebody isn't accidentally creating the // view twice), we check for the RVH Factory, which will be set when we're // making special ones (which go along with the special views). DCHECK(RenderViewHostFactory::has_factory()); return render_widget_host->view(); } RenderWidgetHostViewGtk* view = new RenderWidgetHostViewGtk(render_widget_host); view->InitAsChild(); gfx::NativeView content_view = view->native_view(); g_signal_connect(content_view, "focus", G_CALLBACK(OnFocusThunk), this); g_signal_connect(content_view, "leave-notify-event", G_CALLBACK(OnLeaveNotify), tab_contents()); g_signal_connect(content_view, "motion-notify-event", G_CALLBACK(OnMouseMove), tab_contents()); g_signal_connect(content_view, "scroll-event", G_CALLBACK(OnMouseScroll), tab_contents()); gtk_widget_add_events(content_view, GDK_LEAVE_NOTIFY_MASK | GDK_POINTER_MOTION_MASK); InsertIntoContentArea(content_view); // Renderer target DnD. drag_dest_.reset(new WebDragDestGtk(tab_contents(), content_view)); return view; } gfx::NativeView TabContentsViewGtk::GetNativeView() const { return floating_.get(); } gfx::NativeView TabContentsViewGtk::GetContentNativeView() const { RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView(); if (!rwhv) return NULL; return rwhv->GetNativeView(); } gfx::NativeWindow TabContentsViewGtk::GetTopLevelNativeWindow() const { GtkWidget* window = gtk_widget_get_ancestor(GetNativeView(), GTK_TYPE_WINDOW); return window ? GTK_WINDOW(window) : NULL; } void TabContentsViewGtk::GetContainerBounds(gfx::Rect* out) const { // This is used for positioning the download shelf arrow animation, // as well as sizing some other widgets in Windows. In GTK the size is // managed for us, so it appears to be only used for the download shelf // animation. int x = 0; int y = 0; if (expanded_->window) gdk_window_get_origin(expanded_->window, &x, &y); out->SetRect(x + expanded_->allocation.x, y + expanded_->allocation.y, requested_size_.width(), requested_size_.height()); } void TabContentsViewGtk::SetPageTitle(const std::wstring& title) { // Set the window name to include the page title so it's easier to spot // when debugging (e.g. via xwininfo -tree). gfx::NativeView content_view = GetContentNativeView(); if (content_view && content_view->window) gdk_window_set_title(content_view->window, WideToUTF8(title).c_str()); } void TabContentsViewGtk::OnTabCrashed(base::TerminationStatus status, int error_code) { if (tab_contents() != NULL && !sad_tab_.get()) { sad_tab_.reset(new SadTabGtk( tab_contents(), status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED ? SadTabGtk::KILLED : SadTabGtk::CRASHED)); InsertIntoContentArea(sad_tab_->widget()); gtk_widget_show(sad_tab_->widget()); } } void TabContentsViewGtk::SizeContents(const gfx::Size& size) { // We don't need to manually set the size of of widgets in GTK+, but we do // need to pass the sizing information on to the RWHV which will pass the // sizing information on to the renderer. requested_size_ = size; RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView(); if (rwhv) rwhv->SetSize(size); } void TabContentsViewGtk::Focus() { if (tab_contents()->showing_interstitial_page()) { tab_contents()->interstitial_page()->Focus(); } else if (!constrained_window_) { GtkWidget* widget = GetContentNativeView(); if (widget) gtk_widget_grab_focus(widget); } } void TabContentsViewGtk::SetInitialFocus() { if (tab_contents()->FocusLocationBarByDefault()) tab_contents()->SetFocusToLocationBar(false); else Focus(); } void TabContentsViewGtk::StoreFocus() { focus_store_.Store(GetNativeView()); } void TabContentsViewGtk::RestoreFocus() { if (focus_store_.widget()) gtk_widget_grab_focus(focus_store_.widget()); else SetInitialFocus(); } void TabContentsViewGtk::GetViewBounds(gfx::Rect* out) const { if (!floating_->window) { out->SetRect(0, 0, requested_size_.width(), requested_size_.height()); return; } int x = 0, y = 0, w, h; gdk_window_get_geometry(floating_->window, &x, &y, &w, &h, NULL); out->SetRect(x, y, w, h); } void TabContentsViewGtk::SetFocusedWidget(GtkWidget* widget) { focus_store_.SetWidget(widget); } void TabContentsViewGtk::UpdateDragCursor(WebDragOperation operation) { drag_dest_->UpdateDragStatus(operation); } void TabContentsViewGtk::GotFocus() { // This is only used in the views FocusManager stuff but it bleeds through // all subclasses. http://crbug.com/21875 } // This is called when we the renderer asks us to take focus back (i.e., it has // iterated past the last focusable element on the page). void TabContentsViewGtk::TakeFocus(bool reverse) { if (!tab_contents()->delegate()->TakeFocus(reverse)) { gtk_widget_child_focus(GTK_WIDGET(GetTopLevelNativeWindow()), reverse ? GTK_DIR_TAB_BACKWARD : GTK_DIR_TAB_FORWARD); } } void TabContentsViewGtk::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { switch (type.value) { case NotificationType::TAB_CONTENTS_CONNECTED: { // No need to remove the SadTabGtk's widget from the container since // the new RenderWidgetHostViewGtk instance already removed all the // vbox's children. sad_tab_.reset(); break; } default: NOTREACHED() << "Got a notification we didn't register for."; break; } } void TabContentsViewGtk::ShowContextMenu(const ContextMenuParams& params) { // Find out the RenderWidgetHostView that corresponds to the render widget on // which this context menu is showed, so that we can retrieve the last mouse // down event on the render widget and use it as the timestamp of the // activation event to show the context menu. RenderWidgetHostView* view = NULL; if (params.custom_context.render_widget_id != webkit_glue::CustomContextMenuContext::kCurrentRenderWidget) { IPC::Channel::Listener* listener = tab_contents()->render_view_host()->process()->GetListenerByID( params.custom_context.render_widget_id); if (!listener) { NOTREACHED(); return; } view = static_cast<RenderWidgetHost*>(listener)->view(); } else { view = tab_contents()->GetRenderWidgetHostView(); } RenderWidgetHostViewGtk* view_gtk = static_cast<RenderWidgetHostViewGtk*>(view); if (!view_gtk || !view_gtk->last_mouse_down()) return; context_menu_.reset(new RenderViewContextMenuGtk( tab_contents(), params, view_gtk->last_mouse_down()->time)); context_menu_->Init(); gfx::Rect bounds; GetContainerBounds(&bounds); gfx::Point point = bounds.origin(); point.Offset(params.x, params.y); context_menu_->Popup(point); } void TabContentsViewGtk::ShowPopupMenu(const gfx::Rect& bounds, int item_height, double item_font_size, int selected_item, const std::vector<WebMenuItem>& items, bool right_aligned) { // We are not using external popup menus on Linux, they are rendered by // WebKit. NOTREACHED(); } // Render view DnD ------------------------------------------------------------- void TabContentsViewGtk::StartDragging(const WebDropData& drop_data, WebDragOperationsMask ops, const SkBitmap& image, const gfx::Point& image_offset) { DCHECK(GetContentNativeView()); RenderWidgetHostViewGtk* view_gtk = static_cast<RenderWidgetHostViewGtk*>( tab_contents()->GetRenderWidgetHostView()); if (!view_gtk || !view_gtk->last_mouse_down()) return; drag_source_->StartDragging(drop_data, ops, view_gtk->last_mouse_down(), image, image_offset); } // ----------------------------------------------------------------------------- void TabContentsViewGtk::InsertIntoContentArea(GtkWidget* widget) { gtk_container_add(GTK_CONTAINER(expanded_), widget); } // Called when the content view gtk widget is tabbed to, or after the call to // gtk_widget_child_focus() in TakeFocus(). We return true // and grab focus if we don't have it. The call to // FocusThroughTabTraversal(bool) forwards the "move focus forward" effect to // webkit. gboolean TabContentsViewGtk::OnFocus(GtkWidget* widget, GtkDirectionType focus) { // If we are showing a constrained window, don't allow the native view to take // focus. if (constrained_window_) { // If we return false, it will revert to the default handler, which will // take focus. We don't want that. But if we return true, the event will // stop being propagated, leaving focus wherever it is currently. That is // also bad. So we return false to let the default handler run, but take // focus first so as to trick it into thinking the view was already focused // and allowing the event to propagate. gtk_widget_grab_focus(widget); return FALSE; } // If we already have focus, let the next widget have a shot at it. We will // reach this situation after the call to gtk_widget_child_focus() in // TakeFocus(). if (gtk_widget_is_focus(widget)) return FALSE; gtk_widget_grab_focus(widget); bool reverse = focus == GTK_DIR_TAB_BACKWARD; tab_contents()->FocusThroughTabTraversal(reverse); return TRUE; } void TabContentsViewGtk::OnChildSizeRequest(GtkWidget* widget, GtkWidget* child, GtkRequisition* requisition) { if (tab_contents()->delegate()) { requisition->height += tab_contents()->delegate()->GetExtraRenderViewHeight(); } } void TabContentsViewGtk::OnSizeAllocate(GtkWidget* widget, GtkAllocation* allocation) { int width = allocation->width; int height = allocation->height; // |delegate()| can be NULL here during browser teardown. if (tab_contents()->delegate()) height += tab_contents()->delegate()->GetExtraRenderViewHeight(); gfx::Size size(width, height); requested_size_ = size; // We manually tell our RWHV to resize the renderer content. This avoids // spurious resizes from GTK+. RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView(); if (rwhv) rwhv->SetSize(size); if (tab_contents()->interstitial_page()) tab_contents()->interstitial_page()->SetSize(size); } void TabContentsViewGtk::OnSetFloatingPosition( GtkWidget* floating_container, GtkAllocation* allocation) { if (!constrained_window_) return; // Place each ConstrainedWindow in the center of the view. GtkWidget* widget = constrained_window_->widget(); DCHECK(widget->parent == floating_.get()); GtkRequisition requisition; gtk_widget_size_request(widget, &requisition); GValue value = { 0, }; g_value_init(&value, G_TYPE_INT); int child_x = std::max((allocation->width - requisition.width) / 2, 0); g_value_set_int(&value, child_x); gtk_container_child_set_property(GTK_CONTAINER(floating_container), widget, "x", &value); int child_y = std::max((allocation->height - requisition.height) / 2, 0); g_value_set_int(&value, child_y); gtk_container_child_set_property(GTK_CONTAINER(floating_container), widget, "y", &value); g_value_unset(&value); }