tab_contents_view_gtk.cc revision dc0f95d653279beabeb9817299e2902918ba123e
1// Copyright (c) 2011 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/ui/views/tab_contents/tab_contents_view_gtk.h" 6 7#include <gdk/gdk.h> 8#include <gtk/gtk.h> 9 10#include "base/string_util.h" 11#include "base/utf_string_conversions.h" 12#include "build/build_config.h" 13#include "chrome/browser/download/download_shelf.h" 14#include "chrome/browser/renderer_host/render_widget_host_view_gtk.h" 15#include "chrome/browser/tab_contents/web_drag_dest_gtk.h" 16#include "chrome/browser/ui/gtk/constrained_window_gtk.h" 17#include "chrome/browser/ui/gtk/tab_contents_drag_source.h" 18#include "chrome/browser/ui/views/sad_tab_view.h" 19#include "chrome/browser/ui/views/tab_contents/render_view_context_menu_views.h" 20#include "content/browser/renderer_host/render_view_host.h" 21#include "content/browser/renderer_host/render_view_host_factory.h" 22#include "content/browser/tab_contents/interstitial_page.h" 23#include "content/browser/tab_contents/tab_contents.h" 24#include "content/browser/tab_contents/tab_contents_delegate.h" 25#include "ui/gfx/canvas_skia_paint.h" 26#include "ui/gfx/point.h" 27#include "ui/gfx/rect.h" 28#include "ui/gfx/size.h" 29#include "views/controls/native/native_view_host.h" 30#include "views/focus/view_storage.h" 31#include "views/screen.h" 32#include "views/widget/root_view.h" 33 34using WebKit::WebDragOperation; 35using WebKit::WebDragOperationsMask; 36using WebKit::WebInputEvent; 37 38 39namespace { 40 41// Called when the content view gtk widget is tabbed to, or after the call to 42// gtk_widget_child_focus() in TakeFocus(). We return true 43// and grab focus if we don't have it. The call to 44// FocusThroughTabTraversal(bool) forwards the "move focus forward" effect to 45// webkit. 46gboolean OnFocus(GtkWidget* widget, GtkDirectionType focus, 47 TabContents* tab_contents) { 48 // If we already have focus, let the next widget have a shot at it. We will 49 // reach this situation after the call to gtk_widget_child_focus() in 50 // TakeFocus(). 51 if (gtk_widget_is_focus(widget)) 52 return FALSE; 53 54 gtk_widget_grab_focus(widget); 55 bool reverse = focus == GTK_DIR_TAB_BACKWARD; 56 tab_contents->FocusThroughTabTraversal(reverse); 57 return TRUE; 58} 59 60// Called when the mouse leaves the widget. We notify our delegate. 61// WidgetGtk also defines OnLeaveNotify, so we use the name OnLeaveNotify2 62// here. 63gboolean OnLeaveNotify2(GtkWidget* widget, GdkEventCrossing* event, 64 TabContents* tab_contents) { 65 if (tab_contents->delegate()) 66 tab_contents->delegate()->ContentsMouseEvent( 67 tab_contents, views::Screen::GetCursorScreenPoint(), false); 68 return FALSE; 69} 70 71// Called when the mouse moves within the widget. 72gboolean CallMouseMove(GtkWidget* widget, GdkEventMotion* event, 73 TabContentsViewGtk* tab_contents_view) { 74 return tab_contents_view->OnMouseMove(widget, event); 75} 76 77// See tab_contents_view_gtk.cc for discussion of mouse scroll zooming. 78gboolean OnMouseScroll(GtkWidget* widget, GdkEventScroll* event, 79 TabContents* tab_contents) { 80 if ((event->state & gtk_accelerator_get_default_mod_mask()) == 81 GDK_CONTROL_MASK) { 82 if (tab_contents->delegate()) { 83 if (event->direction == GDK_SCROLL_DOWN) { 84 tab_contents->delegate()->ContentsZoomChange(false); 85 return TRUE; 86 } else if (event->direction == GDK_SCROLL_UP) { 87 tab_contents->delegate()->ContentsZoomChange(true); 88 return TRUE; 89 } 90 } 91 } 92 93 return FALSE; 94} 95 96} // namespace 97 98// static 99TabContentsView* TabContentsView::Create(TabContents* tab_contents) { 100 return new TabContentsViewGtk(tab_contents); 101} 102 103TabContentsViewGtk::TabContentsViewGtk(TabContents* tab_contents) 104 : TabContentsView(tab_contents), 105 views::WidgetGtk(TYPE_CHILD), 106 sad_tab_(NULL), 107 ignore_next_char_event_(false) { 108 drag_source_.reset(new TabContentsDragSource(this)); 109 last_focused_view_storage_id_ = 110 views::ViewStorage::GetInstance()->CreateStorageID(); 111} 112 113TabContentsViewGtk::~TabContentsViewGtk() { 114 // Make sure to remove any stored view we may still have in the ViewStorage. 115 // 116 // It is possible the view went away before us, so we only do this if the 117 // view is registered. 118 views::ViewStorage* view_storage = views::ViewStorage::GetInstance(); 119 if (view_storage->RetrieveView(last_focused_view_storage_id_) != NULL) 120 view_storage->RemoveView(last_focused_view_storage_id_); 121 122 // Just deleting the object doesn't destroy the GtkWidget. We need to do that 123 // manually, and synchronously, since subsequent signal handlers may expect 124 // to locate this object. 125 CloseNow(); 126} 127 128void TabContentsViewGtk::AttachConstrainedWindow( 129 ConstrainedWindowGtk* constrained_window) { 130 DCHECK(find(constrained_windows_.begin(), constrained_windows_.end(), 131 constrained_window) == constrained_windows_.end()); 132 133 constrained_windows_.push_back(constrained_window); 134 AddChild(constrained_window->widget()); 135 136 gfx::Rect bounds; 137 GetContainerBounds(&bounds); 138 SetFloatingPosition(bounds.size()); 139} 140 141void TabContentsViewGtk::RemoveConstrainedWindow( 142 ConstrainedWindowGtk* constrained_window) { 143 std::vector<ConstrainedWindowGtk*>::iterator item = 144 find(constrained_windows_.begin(), constrained_windows_.end(), 145 constrained_window); 146 DCHECK(item != constrained_windows_.end()); 147 RemoveChild((*item)->widget()); 148 constrained_windows_.erase(item); 149} 150 151void TabContentsViewGtk::CreateView(const gfx::Size& initial_size) { 152 set_delete_on_destroy(false); 153 WidgetGtk::Init(NULL, gfx::Rect(0, 0, initial_size.width(), 154 initial_size.height())); 155 // We need to own the widget in order to attach/detach the native view 156 // to container. 157 gtk_object_ref(GTK_OBJECT(GetNativeView())); 158} 159 160RenderWidgetHostView* TabContentsViewGtk::CreateViewForWidget( 161 RenderWidgetHost* render_widget_host) { 162 if (render_widget_host->view()) { 163 // During testing, the view will already be set up in most cases to the 164 // test view, so we don't want to clobber it with a real one. To verify that 165 // this actually is happening (and somebody isn't accidentally creating the 166 // view twice), we check for the RVH Factory, which will be set when we're 167 // making special ones (which go along with the special views). 168 DCHECK(RenderViewHostFactory::has_factory()); 169 return render_widget_host->view(); 170 } 171 172 // If we were showing sad tab, remove it now. 173 if (sad_tab_ != NULL) { 174 SetContentsView(new views::View()); 175 sad_tab_ = NULL; 176 } 177 178 RenderWidgetHostViewGtk* view = 179 new RenderWidgetHostViewGtk(render_widget_host); 180 view->InitAsChild(); 181 g_signal_connect(view->native_view(), "focus", 182 G_CALLBACK(OnFocus), tab_contents()); 183 g_signal_connect(view->native_view(), "leave-notify-event", 184 G_CALLBACK(OnLeaveNotify2), tab_contents()); 185 g_signal_connect(view->native_view(), "motion-notify-event", 186 G_CALLBACK(CallMouseMove), this); 187 g_signal_connect(view->native_view(), "scroll-event", 188 G_CALLBACK(OnMouseScroll), tab_contents()); 189 gtk_widget_add_events(view->native_view(), GDK_LEAVE_NOTIFY_MASK | 190 GDK_POINTER_MOTION_MASK); 191 192 // Renderer target DnD. 193 if (tab_contents()->ShouldAcceptDragAndDrop()) 194 drag_dest_.reset(new WebDragDestGtk(tab_contents(), view->native_view())); 195 196 gtk_fixed_put(GTK_FIXED(GetNativeView()), view->native_view(), 0, 0); 197 return view; 198} 199 200gfx::NativeView TabContentsViewGtk::GetNativeView() const { 201 return WidgetGtk::GetNativeView(); 202} 203 204gfx::NativeView TabContentsViewGtk::GetContentNativeView() const { 205 RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView(); 206 if (!rwhv) 207 return NULL; 208 return rwhv->GetNativeView(); 209} 210 211gfx::NativeWindow TabContentsViewGtk::GetTopLevelNativeWindow() const { 212 GtkWidget* window = gtk_widget_get_ancestor(GetNativeView(), GTK_TYPE_WINDOW); 213 return window ? GTK_WINDOW(window) : NULL; 214} 215 216void TabContentsViewGtk::GetContainerBounds(gfx::Rect* out) const { 217 // Callers expect the requested bounds not the actual bounds. For example, 218 // during init callers expect 0x0, but Gtk layout enforces a min size of 1x1. 219 *out = GetClientAreaScreenBounds(); 220 221 gfx::Size size; 222 WidgetGtk::GetRequestedSize(&size); 223 out->set_size(size); 224} 225 226void TabContentsViewGtk::StartDragging(const WebDropData& drop_data, 227 WebDragOperationsMask ops, 228 const SkBitmap& image, 229 const gfx::Point& image_offset) { 230 drag_source_->StartDragging(drop_data, ops, &last_mouse_down_, 231 image, image_offset); 232} 233 234void TabContentsViewGtk::SetPageTitle(const std::wstring& title) { 235 // Set the window name to include the page title so it's easier to spot 236 // when debugging (e.g. via xwininfo -tree). 237 gfx::NativeView content_view = GetContentNativeView(); 238 if (content_view && content_view->window) 239 gdk_window_set_title(content_view->window, WideToUTF8(title).c_str()); 240} 241 242void TabContentsViewGtk::OnTabCrashed(base::TerminationStatus /* status */, 243 int /* error_code */) { 244} 245 246void TabContentsViewGtk::SizeContents(const gfx::Size& size) { 247 // TODO(brettw) this is a hack and should be removed. See tab_contents_view.h. 248 249 // We're contained in a fixed. To have the fixed relay us out to |size|, set 250 // the size request, which triggers OnSizeAllocate. 251 gtk_widget_set_size_request(GetNativeView(), size.width(), size.height()); 252 253 // We need to send this immediately. 254 RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView(); 255 if (rwhv) 256 rwhv->SetSize(size); 257} 258 259void TabContentsViewGtk::Focus() { 260 if (tab_contents()->interstitial_page()) { 261 tab_contents()->interstitial_page()->Focus(); 262 return; 263 } 264 265 if (tab_contents()->is_crashed() && sad_tab_ != NULL) { 266 sad_tab_->RequestFocus(); 267 return; 268 } 269 270 if (constrained_windows_.size()) { 271 constrained_windows_.back()->FocusConstrainedWindow(); 272 return; 273 } 274 275 RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView(); 276 gtk_widget_grab_focus(rwhv ? rwhv->GetNativeView() : GetNativeView()); 277} 278 279void TabContentsViewGtk::SetInitialFocus() { 280 if (tab_contents()->FocusLocationBarByDefault()) 281 tab_contents()->SetFocusToLocationBar(false); 282 else 283 Focus(); 284} 285 286void TabContentsViewGtk::StoreFocus() { 287 views::ViewStorage* view_storage = views::ViewStorage::GetInstance(); 288 289 if (view_storage->RetrieveView(last_focused_view_storage_id_) != NULL) 290 view_storage->RemoveView(last_focused_view_storage_id_); 291 292 views::FocusManager* focus_manager = 293 views::FocusManager::GetFocusManagerForNativeView(GetNativeView()); 294 if (focus_manager) { 295 // |focus_manager| can be NULL if the tab has been detached but still 296 // exists. 297 views::View* focused_view = focus_manager->GetFocusedView(); 298 if (focused_view) 299 view_storage->StoreView(last_focused_view_storage_id_, focused_view); 300 } 301} 302 303void TabContentsViewGtk::RestoreFocus() { 304 views::ViewStorage* view_storage = views::ViewStorage::GetInstance(); 305 views::View* last_focused_view = 306 view_storage->RetrieveView(last_focused_view_storage_id_); 307 if (!last_focused_view) { 308 SetInitialFocus(); 309 } else { 310 views::FocusManager* focus_manager = 311 views::FocusManager::GetFocusManagerForNativeView(GetNativeView()); 312 313 // If you hit this DCHECK, please report it to Jay (jcampan). 314 DCHECK(focus_manager != NULL) << "No focus manager when restoring focus."; 315 316 if (last_focused_view->IsFocusableInRootView() && focus_manager && 317 focus_manager->ContainsView(last_focused_view)) { 318 last_focused_view->RequestFocus(); 319 } else { 320 // The focused view may not belong to the same window hierarchy (e.g. 321 // if the location bar was focused and the tab is dragged out), or it may 322 // no longer be focusable (e.g. if the location bar was focused and then 323 // we switched to fullscreen mode). In that case we default to the 324 // default focus. 325 SetInitialFocus(); 326 } 327 view_storage->RemoveView(last_focused_view_storage_id_); 328 } 329} 330 331void TabContentsViewGtk::GetViewBounds(gfx::Rect* out) const { 332 *out = GetWindowScreenBounds(); 333} 334 335void TabContentsViewGtk::UpdateDragCursor(WebDragOperation operation) { 336 if (drag_dest_.get()) 337 drag_dest_->UpdateDragStatus(operation); 338} 339 340void TabContentsViewGtk::GotFocus() { 341 if (tab_contents()->delegate()) 342 tab_contents()->delegate()->TabContentsFocused(tab_contents()); 343} 344 345void TabContentsViewGtk::TakeFocus(bool reverse) { 346 if (tab_contents()->delegate() && 347 !tab_contents()->delegate()->TakeFocus(reverse)) { 348 349 views::FocusManager* focus_manager = 350 views::FocusManager::GetFocusManagerForNativeView(GetNativeView()); 351 352 // We may not have a focus manager if the tab has been switched before this 353 // message arrived. 354 if (focus_manager) 355 focus_manager->AdvanceFocus(reverse); 356 } 357} 358 359void TabContentsViewGtk::ShowContextMenu(const ContextMenuParams& params) { 360 // Allow delegates to handle the context menu operation first. 361 if (tab_contents()->delegate()->HandleContextMenu(params)) 362 return; 363 364 context_menu_.reset(new RenderViewContextMenuViews(tab_contents(), params)); 365 context_menu_->Init(); 366 367 gfx::Point screen_point(params.x, params.y); 368 views::View::ConvertPointToScreen(GetRootView(), &screen_point); 369 370 // Enable recursive tasks on the message loop so we can get updates while 371 // the context menu is being displayed. 372 bool old_state = MessageLoop::current()->NestableTasksAllowed(); 373 MessageLoop::current()->SetNestableTasksAllowed(true); 374 context_menu_->RunMenuAt(screen_point.x(), screen_point.y()); 375 MessageLoop::current()->SetNestableTasksAllowed(old_state); 376} 377 378void TabContentsViewGtk::ShowPopupMenu(const gfx::Rect& bounds, 379 int item_height, 380 double item_font_size, 381 int selected_item, 382 const std::vector<WebMenuItem>& items, 383 bool right_aligned) { 384 // External popup menus are only used on Mac. 385 NOTREACHED(); 386} 387 388gboolean TabContentsViewGtk::OnButtonPress(GtkWidget* widget, 389 GdkEventButton* event) { 390 last_mouse_down_ = *event; 391 return views::WidgetGtk::OnButtonPress(widget, event); 392} 393 394void TabContentsViewGtk::OnSizeAllocate(GtkWidget* widget, 395 GtkAllocation* allocation) { 396 gfx::Size new_size(allocation->width, allocation->height); 397 398 // Always call WasSized() to allow checking to make sure the 399 // RenderWidgetHostView is the right size. 400 WasSized(new_size); 401} 402 403gboolean TabContentsViewGtk::OnPaint(GtkWidget* widget, GdkEventExpose* event) { 404 if (tab_contents()->render_view_host() && 405 !tab_contents()->render_view_host()->IsRenderViewLive()) { 406 base::TerminationStatus status = 407 tab_contents()->render_view_host()->render_view_termination_status(); 408 SadTabView::Kind kind = 409 status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED ? 410 SadTabView::KILLED : SadTabView::CRASHED; 411 sad_tab_ = new SadTabView(tab_contents(), kind); 412 SetContentsView(sad_tab_); 413 gfx::Rect bounds = GetWindowScreenBounds(); 414 sad_tab_->SetBoundsRect(gfx::Rect(0, 0, bounds.width(), bounds.height())); 415 gfx::CanvasSkiaPaint canvas(event); 416 sad_tab_->Paint(&canvas); 417 } 418 return false; // False indicates other widgets should get the event as well. 419} 420 421void TabContentsViewGtk::OnShow(GtkWidget* widget) { 422 WasShown(); 423} 424 425void TabContentsViewGtk::OnHide(GtkWidget* widget) { 426 WasHidden(); 427} 428 429void TabContentsViewGtk::WasHidden() { 430 tab_contents()->HideContents(); 431} 432 433void TabContentsViewGtk::WasShown() { 434 tab_contents()->ShowContents(); 435} 436 437void TabContentsViewGtk::WasSized(const gfx::Size& size) { 438 // We have to check that the RenderWidgetHostView is the proper size. 439 // It can be wrong in cases where the renderer has died and the host 440 // view needed to be recreated. 441 bool needs_resize = size != size_; 442 443 if (needs_resize) { 444 size_ = size; 445 if (tab_contents()->interstitial_page()) 446 tab_contents()->interstitial_page()->SetSize(size); 447 } 448 449 RenderWidgetHostView* rwhv = tab_contents()->GetRenderWidgetHostView(); 450 if (rwhv && rwhv->GetViewBounds().size() != size) 451 rwhv->SetSize(size); 452 453 if (needs_resize) 454 SetFloatingPosition(size); 455} 456 457void TabContentsViewGtk::SetFloatingPosition(const gfx::Size& size) { 458 // Place each ConstrainedWindow in the center of the view. 459 int half_view_width = size.width() / 2; 460 461 typedef std::vector<ConstrainedWindowGtk*>::iterator iterator; 462 463 for (iterator f = constrained_windows_.begin(), 464 l = constrained_windows_.end(); f != l; ++f) { 465 GtkWidget* widget = (*f)->widget(); 466 467 GtkRequisition requisition; 468 gtk_widget_size_request(widget, &requisition); 469 470 int child_x = std::max(half_view_width - (requisition.width / 2), 0); 471 PositionChild(widget, child_x, 0, 0, 0); 472 } 473} 474 475// Called when the mouse moves within the widget. We notify SadTabView if it's 476// not NULL, else our delegate. 477gboolean TabContentsViewGtk::OnMouseMove(GtkWidget* widget, 478 GdkEventMotion* event) { 479 if (sad_tab_ != NULL) 480 WidgetGtk::OnMotionNotify(widget, event); 481 else if (tab_contents()->delegate()) 482 tab_contents()->delegate()->ContentsMouseEvent( 483 tab_contents(), views::Screen::GetCursorScreenPoint(), true); 484 return FALSE; 485} 486