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