| // 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/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/renderer_host/render_widget_host_view_gtk.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/content_notification_types.h" |
| #include "content/common/notification_source.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) |
| : tab_contents_(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, content::NOTIFICATION_TAB_CONTENTS_CONNECTED, |
| Source<TabContents>(tab_contents)); |
| drag_source_.reset(new TabContentsDragSource(tab_contents)); |
| } |
| |
| 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::RenderViewCreated(RenderViewHost* host) { |
| } |
| |
| 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::UpdatePreferredSize(const gfx::Size& pref_size) { |
| } |
| |
| bool TabContentsViewGtk::IsDoingDrag() const { |
| return false; |
| } |
| |
| void TabContentsViewGtk::CancelDragAndCloseTab() { |
| } |
| |
| bool TabContentsViewGtk::IsEventTracking() const { |
| return false; |
| } |
| |
| void TabContentsViewGtk::CloseTabAfterEventTracking() { |
| } |
| |
| 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(int type, |
| const NotificationSource& source, |
| const NotificationDetails& details) { |
| switch (type) { |
| case content::NOTIFICATION_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::CreateNewWindow( |
| int route_id, |
| const ViewHostMsg_CreateWindow_Params& params) { |
| delegate_view_helper_.CreateNewWindowFromTabContents( |
| tab_contents_, route_id, params); |
| } |
| |
| void TabContentsViewGtk::CreateNewWidget( |
| int route_id, WebKit::WebPopupType popup_type) { |
| delegate_view_helper_.CreateNewWidget(route_id, popup_type, |
| tab_contents_->render_view_host()->process()); |
| } |
| |
| void TabContentsViewGtk::CreateNewFullscreenWidget(int route_id) { |
| delegate_view_helper_.CreateNewFullscreenWidget( |
| route_id, tab_contents_->render_view_host()->process()); |
| } |
| |
| void TabContentsViewGtk::ShowCreatedWindow(int route_id, |
| WindowOpenDisposition disposition, |
| const gfx::Rect& initial_pos, |
| bool user_gesture) { |
| delegate_view_helper_.ShowCreatedWindow( |
| tab_contents_, route_id, disposition, initial_pos, user_gesture); |
| } |
| |
| void TabContentsViewGtk::ShowCreatedWidget( |
| int route_id, const gfx::Rect& initial_pos) { |
| delegate_view_helper_.ShowCreatedWidget( |
| tab_contents_, route_id, initial_pos); |
| } |
| |
| void TabContentsViewGtk::ShowCreatedFullscreenWidget(int route_id) { |
| delegate_view_helper_.ShowCreatedFullscreenWidget(tab_contents_, route_id); |
| } |
| |
| 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); |
| } |