| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2010 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/chromeos/app_launcher.h" | |
| 6 | |
| 7 #include <string> | |
| 8 #include <vector> | |
| 9 | |
| 10 #include "app/gfx/canvas.h" | |
| 11 #include "app/resource_bundle.h" | |
| 12 #include "base/command_line.h" | |
| 13 #include "base/message_loop.h" | |
| 14 #include "base/string_util.h" | |
| 15 #include "chrome/browser/autocomplete/autocomplete_edit.h" | |
| 16 #include "chrome/browser/autocomplete/autocomplete_edit_view_gtk.h" | |
| 17 #include "chrome/browser/browser.h" | |
| 18 #include "chrome/browser/browser_list.h" | |
| 19 #include "chrome/browser/browser_window.h" | |
| 20 #include "chrome/browser/bubble_positioner.h" | |
| 21 #include "chrome/browser/chromeos/frame/browser_view.h" | |
| 22 #include "chrome/browser/chromeos/status/status_area_view.h" | |
| 23 #include "chrome/browser/chromeos/wm_ipc.h" | |
| 24 #include "chrome/browser/in_process_webkit/dom_storage_context.h" | |
| 25 #include "chrome/browser/in_process_webkit/webkit_context.h" | |
| 26 #include "chrome/browser/profile.h" | |
| 27 #include "chrome/browser/renderer_host/render_view_host.h" | |
| 28 #include "chrome/browser/renderer_host/render_view_host_factory.h" | |
| 29 #include "chrome/browser/renderer_host/render_widget_host_view.h" | |
| 30 #include "chrome/browser/renderer_host/render_widget_host_view_gtk.h" | |
| 31 #include "chrome/browser/renderer_host/site_instance.h" | |
| 32 #include "chrome/browser/renderer_preferences_util.h" | |
| 33 #include "chrome/browser/tab_contents/render_view_host_delegate_helper.h" | |
| 34 #include "chrome/browser/tab_contents/tab_contents.h" | |
| 35 #include "chrome/browser/views/bubble_border.h" | |
| 36 #include "gfx/insets.h" | |
| 37 #include "gfx/point.h" | |
| 38 #include "grit/app_resources.h" | |
| 39 #include "grit/generated_resources.h" | |
| 40 #include "grit/theme_resources.h" | |
| 41 #include "third_party/skia/include/core/SkBitmap.h" | |
| 42 #include "views/background.h" | |
| 43 #include "views/controls/native/native_view_host.h" | |
| 44 #include "views/painter.h" | |
| 45 #include "views/screen.h" | |
| 46 #include "views/widget/root_view.h" | |
| 47 #include "views/widget/widget_gtk.h" | |
| 48 | |
| 49 namespace { | |
| 50 | |
| 51 // Padding & margins for the navigation entry. | |
| 52 const int kNavigationEntryPadding = 2; | |
| 53 const int kNavigationEntryXMargin = 3; | |
| 54 const int kNavigationEntryYMargin = 1; | |
| 55 | |
| 56 // NavigationBar size. | |
| 57 const int kNavigationBarHeight = 25; | |
| 58 | |
| 59 // Padding for the bubble info window. | |
| 60 const int kBubbleWindowXPadding = 6; | |
| 61 const int kBubbleWindowYPadding = 16; | |
| 62 | |
| 63 // Command line switch for specifying url of the page. | |
| 64 const wchar_t kURLSwitch[] = L"main-menu-url"; | |
| 65 | |
| 66 // Command line switch for specifying the size of the main menu. The default is | |
| 67 // full screen. | |
| 68 const wchar_t kMenuSizeSwitch[] = L"main-menu-size"; | |
| 69 | |
| 70 // URL of the page to load. This is ignored if kURLSwitch is specified. | |
| 71 const char kMenuURL[] = "http://goto.ext.google.com/crux-home"; | |
| 72 | |
| 73 // Returns the URL of the menu. | |
| 74 static GURL GetMenuURL() { | |
| 75 std::wstring url_string = | |
| 76 CommandLine::ForCurrentProcess()->GetSwitchValue(kURLSwitch); | |
| 77 if (!url_string.empty()) | |
| 78 return GURL(WideToUTF8(url_string)); | |
| 79 return GURL(kMenuURL); | |
| 80 } | |
| 81 | |
| 82 // RenderWidgetHostViewGtk propagates the mouse press events (see | |
| 83 // render_widget_host_view_gtk.cc). We subclass to stop the propagation here, | |
| 84 // as if the click event was propagated it would reach the TopContainer view and | |
| 85 // it would close the popup. | |
| 86 class RWHVNativeViewHost : public views::NativeViewHost { | |
| 87 public: | |
| 88 RWHVNativeViewHost() {} | |
| 89 | |
| 90 virtual bool OnMousePressed(const views::MouseEvent& event) { return true; } | |
| 91 | |
| 92 private: | |
| 93 DISALLOW_COPY_AND_ASSIGN(RWHVNativeViewHost); | |
| 94 }; | |
| 95 | |
| 96 } // namspace | |
| 97 | |
| 98 namespace chromeos { | |
| 99 | |
| 100 //////////////////////////////////////////////////////////////////////////////// | |
| 101 // NavigationBar | |
| 102 // | |
| 103 // A navigation bar that is shown in the app launcher in compact navigation bar | |
| 104 // mode. | |
| 105 | |
| 106 class NavigationBar : public views::View, | |
| 107 public AutocompleteEditController, | |
| 108 public BubblePositioner { | |
| 109 public: | |
| 110 explicit NavigationBar(AppLauncher* app_launcher) | |
| 111 : views::View(), | |
| 112 app_launcher_(app_launcher), | |
| 113 location_entry_view_(NULL) { | |
| 114 SetFocusable(true); | |
| 115 location_entry_view_ = new views::NativeViewHost; | |
| 116 AddChildView(location_entry_view_); | |
| 117 } | |
| 118 | |
| 119 virtual ~NavigationBar() { | |
| 120 if (location_entry_view_->native_view()) | |
| 121 location_entry_view_->Detach(); | |
| 122 } | |
| 123 | |
| 124 // views::View overrides. | |
| 125 virtual void Focus() { | |
| 126 location_entry_->SetFocus(); | |
| 127 location_entry_->SelectAll(true); | |
| 128 } | |
| 129 | |
| 130 virtual void Layout() { | |
| 131 gfx::Rect bounds = GetLocalBounds(false); | |
| 132 | |
| 133 const int vertical_margin = | |
| 134 kNavigationEntryPadding + kNavigationEntryYMargin; | |
| 135 | |
| 136 location_entry_view_->SetBounds( | |
| 137 bounds.x() + kNavigationEntryXMargin + kNavigationEntryPadding, | |
| 138 bounds.y() + vertical_margin, | |
| 139 bounds.width() - 2 * (kNavigationEntryPadding + | |
| 140 kNavigationEntryXMargin), | |
| 141 bounds.height() - vertical_margin * 2); | |
| 142 } | |
| 143 | |
| 144 virtual void Paint(gfx::Canvas* canvas) { | |
| 145 const int padding = kNavigationEntryPadding; | |
| 146 canvas->FillRectInt(SK_ColorWHITE, 0, 0, width(), height()); | |
| 147 // Draw border around the entry. | |
| 148 gfx::Rect bounds = location_entry_view_->GetBounds( | |
| 149 views::View::APPLY_MIRRORING_TRANSFORMATION); | |
| 150 canvas->DrawRectInt(SK_ColorGRAY, | |
| 151 bounds.x() - padding, | |
| 152 bounds.y() - padding, | |
| 153 bounds.width() + padding * 2, | |
| 154 bounds.height() + padding * 2); | |
| 155 } | |
| 156 | |
| 157 // BubblePositioner implementation. | |
| 158 virtual gfx::Rect GetLocationStackBounds() const { | |
| 159 gfx::Rect bounds = location_entry_view_->GetBounds( | |
| 160 views::View::APPLY_MIRRORING_TRANSFORMATION); | |
| 161 gfx::Point origin(bounds.x(), bounds.bottom() + kNavigationEntryPadding); | |
| 162 views::View::ConvertPointToScreen(this, &origin); | |
| 163 gfx::Rect rect = gfx::Rect(origin, gfx::Size(500, 0)); | |
| 164 if (UILayoutIsRightToLeft()) { | |
| 165 // Align the window to the right side of the entry view when | |
| 166 // UI is RTL mode. | |
| 167 rect.set_x(rect.x() - (rect.width() - location_entry_view_->width())); | |
| 168 } | |
| 169 return rect; | |
| 170 } | |
| 171 | |
| 172 // AutocompleteController implementation. | |
| 173 virtual void OnAutocompleteAccept(const GURL& url, | |
| 174 WindowOpenDisposition disposition, | |
| 175 PageTransition::Type transition, | |
| 176 const GURL& alternate_nav_url) { | |
| 177 app_launcher_->AddTabWithURL(url, transition); | |
| 178 app_launcher_->Hide(); | |
| 179 } | |
| 180 virtual void OnChanged() {} | |
| 181 virtual void OnInputInProgress(bool in_progress) {} | |
| 182 virtual void OnKillFocus() {} | |
| 183 virtual void OnSetFocus() { | |
| 184 views::FocusManager* focus_manager = GetFocusManager(); | |
| 185 if (!focus_manager) { | |
| 186 NOTREACHED(); | |
| 187 return; | |
| 188 } | |
| 189 focus_manager->SetFocusedView(this); | |
| 190 } | |
| 191 virtual SkBitmap GetFavIcon() const { | |
| 192 return SkBitmap(); | |
| 193 } | |
| 194 virtual std::wstring GetTitle() const { | |
| 195 return std::wstring(); | |
| 196 } | |
| 197 | |
| 198 // AutocompleteEditView depends on the browser instance. | |
| 199 // Create new one when the browser instance changes. | |
| 200 void Update(Browser* browser) { | |
| 201 // Detach the native view if any. | |
| 202 if (location_entry_view_ && location_entry_view_->native_view()) | |
| 203 location_entry_view_->Detach(); | |
| 204 | |
| 205 location_entry_.reset(new AutocompleteEditViewGtk( | |
| 206 this, browser->toolbar_model(), browser->profile(), | |
| 207 browser->command_updater(), false, this)); | |
| 208 location_entry_->Init(); | |
| 209 gtk_widget_show_all(location_entry_->widget()); | |
| 210 gtk_widget_hide(location_entry_->widget()); | |
| 211 | |
| 212 location_entry_view_->set_focus_view(this); | |
| 213 location_entry_view_->Attach(location_entry_->widget()); | |
| 214 } | |
| 215 | |
| 216 private: | |
| 217 AppLauncher* app_launcher_; | |
| 218 views::NativeViewHost* location_entry_view_; | |
| 219 scoped_ptr<AutocompleteEditViewGtk> location_entry_; | |
| 220 | |
| 221 DISALLOW_COPY_AND_ASSIGN(NavigationBar); | |
| 222 }; | |
| 223 | |
| 224 //////////////////////////////////////////////////////////////////////////////// | |
| 225 // TopContainer | |
| 226 // | |
| 227 // A view that grays-out the browser and contains the navigation bar and | |
| 228 // renderer view. | |
| 229 | |
| 230 AppLauncher::TopContainer::TopContainer(AppLauncher* app_launcher) | |
| 231 : app_launcher_(app_launcher) { | |
| 232 // Use a transparent black background so the browser appears grayed-out. | |
| 233 set_background(views::Background::CreateSolidBackground(0, 0, 0, 100)); | |
| 234 } | |
| 235 | |
| 236 void AppLauncher::TopContainer::Layout() { | |
| 237 if (bounds().IsEmpty()) | |
| 238 return; | |
| 239 | |
| 240 // We only expect to contain the BubbleContents. | |
| 241 DCHECK(GetChildViewCount() == 1); | |
| 242 GetChildViewAt(0)->SetBounds(kBubbleWindowXPadding, kBubbleWindowYPadding, | |
| 243 width() * 2 / 3, height() * 4 / 5); | |
| 244 } | |
| 245 | |
| 246 bool AppLauncher::TopContainer::OnMousePressed(const views::MouseEvent& event) { | |
| 247 // Clicking outside the bubble closes the bubble. | |
| 248 app_launcher_->Hide(); | |
| 249 return false; | |
| 250 } | |
| 251 | |
| 252 //////////////////////////////////////////////////////////////////////////////// | |
| 253 // BubbleContainer | |
| 254 // | |
| 255 // The view that contains the navigation bar and render view. It has a bubble | |
| 256 // border. | |
| 257 | |
| 258 AppLauncher::BubbleContainer::BubbleContainer(AppLauncher* app_launcher) | |
| 259 : app_launcher_(app_launcher) { | |
| 260 BubbleBorder* bubble_border = new BubbleBorder(); | |
| 261 bubble_border->set_arrow_location(BubbleBorder::TOP_LEFT); | |
| 262 set_border(bubble_border); | |
| 263 set_background(new BubbleBackground(bubble_border)); | |
| 264 } | |
| 265 | |
| 266 void AppLauncher::BubbleContainer::Layout() { | |
| 267 if (bounds().IsEmpty() || GetChildViewCount() == 0) | |
| 268 return; | |
| 269 | |
| 270 gfx::Rect bounds = GetLocalBounds(false); | |
| 271 // TODO(jcampan): figure-out why we need to inset for the contained view not | |
| 272 // to paint over the bubble border. | |
| 273 bounds.Inset(2, 2); | |
| 274 | |
| 275 app_launcher_->navigation_bar_->SetBounds(bounds.x(), bounds.y(), | |
| 276 bounds.width(), | |
| 277 kNavigationBarHeight); | |
| 278 int render_y = app_launcher_->navigation_bar_->bounds().bottom(); | |
| 279 gfx::Size rwhv_size = | |
| 280 gfx::Size(bounds.width(), | |
| 281 std::max(0, bounds.height() - render_y + bounds.y())); | |
| 282 app_launcher_->render_view_container_->SetBounds(bounds.x(), render_y, | |
| 283 rwhv_size.width(), | |
| 284 rwhv_size.height()); | |
| 285 app_launcher_->rwhv_->SetSize(rwhv_size); | |
| 286 } | |
| 287 | |
| 288 //////////////////////////////////////////////////////////////////////////////// | |
| 289 // AppLauncher | |
| 290 | |
| 291 AppLauncher::AppLauncher(Browser* browser) | |
| 292 : browser_(browser), | |
| 293 popup_(NULL), | |
| 294 site_instance_(NULL), | |
| 295 contents_rvh_(NULL), | |
| 296 rwhv_(NULL), | |
| 297 ALLOW_THIS_IN_INITIALIZER_LIST(tab_contents_delegate_(this)), | |
| 298 ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), | |
| 299 top_container_(NULL), | |
| 300 bubble_container_(NULL), | |
| 301 navigation_bar_(NULL), | |
| 302 render_view_container_(NULL), | |
| 303 has_shown_(false) { | |
| 304 popup_ = new views::WidgetGtk(views::WidgetGtk::TYPE_WINDOW); | |
| 305 // The background image has transparency, so we make the window transparent. | |
| 306 popup_->MakeTransparent(); | |
| 307 popup_->Init(NULL, gfx::Rect()); | |
| 308 WmIpc::instance()->SetWindowType( | |
| 309 popup_->GetNativeView(), | |
| 310 WmIpc::WINDOW_TYPE_CHROME_INFO_BUBBLE, | |
| 311 NULL); | |
| 312 | |
| 313 // Register Esc as an accelerator for closing the app launcher. | |
| 314 views::FocusManager* focus_manager = popup_->GetFocusManager(); | |
| 315 focus_manager->RegisterAccelerator(views::Accelerator(base::VKEY_ESCAPE, | |
| 316 false, false, false), | |
| 317 this); | |
| 318 | |
| 319 top_container_ = new TopContainer(this); | |
| 320 popup_->SetContentsView(top_container_); | |
| 321 | |
| 322 bubble_container_ = new BubbleContainer(this); | |
| 323 top_container_->AddChildView(bubble_container_); | |
| 324 navigation_bar_ = new NavigationBar(this); | |
| 325 bubble_container_->AddChildView(navigation_bar_); | |
| 326 | |
| 327 GURL menu_url(GetMenuURL()); | |
| 328 DCHECK(BrowserList::begin() != BrowserList::end()); | |
| 329 // TODO(sky): this shouldn't pick a random profile to use. | |
| 330 Profile* profile = (*BrowserList::begin())->profile(); | |
| 331 int64 session_storage_namespace_id = profile->GetWebKitContext()-> | |
| 332 dom_storage_context()->AllocateSessionStorageNamespaceId(); | |
| 333 site_instance_ = SiteInstance::CreateSiteInstanceForURL(profile, menu_url); | |
| 334 contents_rvh_ = new RenderViewHost(site_instance_, this, MSG_ROUTING_NONE, | |
| 335 session_storage_namespace_id); | |
| 336 | |
| 337 rwhv_ = new RenderWidgetHostViewGtk(contents_rvh_); | |
| 338 rwhv_->InitAsChild(); | |
| 339 contents_rvh_->CreateRenderView(profile->GetRequestContext()); | |
| 340 | |
| 341 render_view_container_ = new RWHVNativeViewHost; | |
| 342 bubble_container_->AddChildView(render_view_container_); | |
| 343 render_view_container_->Attach(rwhv_->GetNativeView()); | |
| 344 contents_rvh_->NavigateToURL(menu_url); | |
| 345 | |
| 346 navigation_bar_->Update(browser); | |
| 347 // Set the transient window so that ChromeOS WM treat this | |
| 348 // as if a popup window. | |
| 349 gtk_window_set_transient_for( | |
| 350 GTK_WINDOW(popup_->GetNativeView()), | |
| 351 GTK_WINDOW(browser_->window()->GetNativeHandle())); | |
| 352 | |
| 353 ActiveWindowWatcherX::AddObserver(this); | |
| 354 } | |
| 355 | |
| 356 AppLauncher::~AppLauncher() { | |
| 357 contents_rvh_->Shutdown(); | |
| 358 popup_->CloseNow(); | |
| 359 ActiveWindowWatcherX::RemoveObserver(this); | |
| 360 } | |
| 361 | |
| 362 void AppLauncher::Update() { | |
| 363 popup_->SetBounds(browser_->window()->GetRestoredBounds()); | |
| 364 top_container_->Layout(); | |
| 365 } | |
| 366 | |
| 367 void AppLauncher::Show() { | |
| 368 Cleanup(); | |
| 369 | |
| 370 Update(); | |
| 371 popup_->Show(); | |
| 372 | |
| 373 GtkWidget* rwhv_widget = rwhv_->GetNativeView(); | |
| 374 if (!has_shown_) { | |
| 375 has_shown_ = true; | |
| 376 gtk_widget_realize(rwhv_widget); | |
| 377 } | |
| 378 } | |
| 379 | |
| 380 void AppLauncher::ActiveWindowChanged(GdkWindow* active_window) { | |
| 381 if (!popup_->IsActive()) | |
| 382 Hide(); | |
| 383 else | |
| 384 navigation_bar_->RequestFocus(); | |
| 385 } | |
| 386 | |
| 387 bool AppLauncher::AcceleratorPressed(const views::Accelerator& accelerator) { | |
| 388 DCHECK(accelerator.GetKeyCode() == base::VKEY_ESCAPE); | |
| 389 popup_->Hide(); | |
| 390 return true; | |
| 391 } | |
| 392 | |
| 393 void AppLauncher::Hide() { | |
| 394 popup_->Hide(); | |
| 395 // The stack may have pending_contents_ on it. Delay deleting the | |
| 396 // pending_contents_ as TabContents doesn't deal well with being deleted | |
| 397 // while on the stack. | |
| 398 MessageLoop::current()->PostTask(FROM_HERE, | |
| 399 method_factory_.NewRunnableMethod(&AppLauncher::Cleanup)); | |
| 400 } | |
| 401 | |
| 402 void AppLauncher::Cleanup() { | |
| 403 pending_contents_.reset(NULL); | |
| 404 method_factory_.RevokeAll(); | |
| 405 } | |
| 406 | |
| 407 void AppLauncher::RequestMove(const gfx::Rect& new_bounds) { | |
| 408 // Invoking PositionChild results in a gtk signal that triggers attempting to | |
| 409 // to resize the window. We need to set the size request so that it resizes | |
| 410 // correctly when this happens. | |
| 411 gtk_widget_set_size_request(popup_->GetNativeView(), | |
| 412 new_bounds.width(), new_bounds.height()); | |
| 413 popup_->SetBounds(new_bounds); | |
| 414 } | |
| 415 | |
| 416 RendererPreferences AppLauncher::GetRendererPrefs(Profile* profile) const { | |
| 417 RendererPreferences preferences; | |
| 418 renderer_preferences_util::UpdateFromSystemSettings(&preferences, profile); | |
| 419 return preferences; | |
| 420 } | |
| 421 | |
| 422 void AppLauncher::AddTabWithURL(const GURL& url, | |
| 423 PageTransition::Type transition) { | |
| 424 switch (StatusAreaView::GetOpenTabsMode()) { | |
| 425 case StatusAreaView::OPEN_TABS_ON_LEFT: { | |
| 426 // Add the new tab at the first non-pinned location. | |
| 427 int index = browser_->tabstrip_model()->IndexOfFirstNonMiniTab(); | |
| 428 browser_->AddTabWithURL(url, GURL(), transition, | |
| 429 true, index, true, NULL); | |
| 430 break; | |
| 431 } | |
| 432 case StatusAreaView::OPEN_TABS_CLOBBER: { | |
| 433 browser_->GetSelectedTabContents()->controller().LoadURL( | |
| 434 url, GURL(), transition); | |
| 435 break; | |
| 436 } | |
| 437 case StatusAreaView::OPEN_TABS_ON_RIGHT: { | |
| 438 browser_->AddTabWithURL(url, GURL(), transition, true, -1, true, NULL); | |
| 439 break; | |
| 440 } | |
| 441 } | |
| 442 } | |
| 443 | |
| 444 void AppLauncher::CreateNewWindow(int route_id) { | |
| 445 if (pending_contents_.get()) { | |
| 446 NOTREACHED(); | |
| 447 return; | |
| 448 } | |
| 449 | |
| 450 helper_.CreateNewWindow(route_id, browser_->profile(), site_instance_, | |
| 451 DOMUIFactory::GetDOMUIType(GURL(GetMenuURL())), | |
| 452 NULL); | |
| 453 pending_contents_.reset(helper_.GetCreatedWindow(route_id)); | |
| 454 pending_contents_->set_delegate(&tab_contents_delegate_); | |
| 455 } | |
| 456 | |
| 457 void AppLauncher::ShowCreatedWindow(int route_id, | |
| 458 WindowOpenDisposition disposition, | |
| 459 const gfx::Rect& initial_pos, | |
| 460 bool user_gesture) { | |
| 461 if (disposition == NEW_POPUP) { | |
| 462 pending_contents_->set_delegate(NULL); | |
| 463 browser_->GetSelectedTabContents()->AddNewContents( | |
| 464 pending_contents_.release(), disposition, initial_pos, user_gesture); | |
| 465 Hide(); | |
| 466 } | |
| 467 } | |
| 468 | |
| 469 void AppLauncher::StartDragging(const WebDropData& drop_data, | |
| 470 WebKit::WebDragOperationsMask allowed_ops, | |
| 471 const SkBitmap& image, | |
| 472 const gfx::Point& image_offset) { | |
| 473 // We're not going to do any drag & drop, but we have to tell the renderer the | |
| 474 // drag & drop ended, othewise the renderer thinks the drag operation is | |
| 475 // underway and mouse events won't work. | |
| 476 contents_rvh_->DragSourceSystemDragEnded(); | |
| 477 } | |
| 478 | |
| 479 AppLauncher::TabContentsDelegateImpl::TabContentsDelegateImpl( | |
| 480 AppLauncher* app_launcher) | |
| 481 : app_launcher_(app_launcher) { | |
| 482 } | |
| 483 | |
| 484 void AppLauncher::TabContentsDelegateImpl::OpenURLFromTab( | |
| 485 TabContents* source, | |
| 486 const GURL& url, | |
| 487 const GURL& referrer, | |
| 488 WindowOpenDisposition disposition, | |
| 489 PageTransition::Type transition) { | |
| 490 app_launcher_->browser_->OpenURL(url, referrer, NEW_FOREGROUND_TAB, | |
| 491 PageTransition::LINK); | |
| 492 app_launcher_->Hide(); | |
| 493 } | |
| 494 | |
| 495 } // namespace chromeos | |
| OLD | NEW |