// Copyright 2013 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 "content/shell/browser/shell.h"
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include "base/logging.h"
#include "base/strings/string_piece.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/native_web_keyboard_event.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
#include "content/public/common/renderer_preferences.h"
#include "content/shell/browser/shell_browser_context.h"
#include "content/shell/browser/shell_content_browser_client.h"
namespace content {
namespace {
// Callback for Debug > Show web inspector... menu item.
gboolean ShowWebInspectorActivated(GtkWidget* widget, Shell* shell) {
shell->ShowDevTools();
return FALSE; // Don't stop this message.
}
GtkWidget* AddMenuEntry(GtkWidget* menu_widget, const char* text,
GCallback callback, Shell* shell) {
GtkWidget* entry = gtk_menu_item_new_with_label(text);
g_signal_connect(entry, "activate", callback, shell);
gtk_menu_shell_append(GTK_MENU_SHELL(menu_widget), entry);
return entry;
}
GtkWidget* CreateMenu(GtkWidget* menu_bar, const char* text) {
GtkWidget* menu_widget = gtk_menu_new();
GtkWidget* menu_header = gtk_menu_item_new_with_label(text);
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_header), menu_widget);
gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), menu_header);
return menu_widget;
}
GtkWidget* CreateMenuBar(Shell* shell) {
GtkWidget* menu_bar = gtk_menu_bar_new();
GtkWidget* debug_menu = CreateMenu(menu_bar, "Debug");
AddMenuEntry(debug_menu, "Show web inspector...",
G_CALLBACK(ShowWebInspectorActivated), shell);
return menu_bar;
}
} // namespace
void Shell::PlatformInitialize(const gfx::Size& default_window_size) {
}
void Shell::PlatformExit() {
}
void Shell::PlatformCleanUp() {
// Nothing to clean up; GTK will clean up the widgets shortly after.
}
void Shell::PlatformEnableUIControl(UIControl control, bool is_enabled) {
if (headless_)
return;
GtkToolItem* item = NULL;
switch (control) {
case BACK_BUTTON:
item = back_button_;
break;
case FORWARD_BUTTON:
item = forward_button_;
break;
case STOP_BUTTON:
item = stop_button_;
break;
default:
NOTREACHED() << "Unknown UI control";
return;
}
gtk_widget_set_sensitive(GTK_WIDGET(item), is_enabled);
}
void Shell::PlatformSetAddressBarURL(const GURL& url) {
if (headless_)
return;
gtk_entry_set_text(GTK_ENTRY(url_edit_view_), url.spec().c_str());
}
void Shell::PlatformSetIsLoading(bool loading) {
if (headless_)
return;
if (loading)
gtk_spinner_start(GTK_SPINNER(spinner_));
else
gtk_spinner_stop(GTK_SPINNER(spinner_));
}
void Shell::PlatformCreateWindow(int width, int height) {
ui_elements_height_ = 0;
if (headless_) {
content_size_ = gfx::Size(width, height);
return;
}
window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
gtk_window_set_title(window_, "Content Shell");
g_signal_connect(G_OBJECT(window_), "destroy",
G_CALLBACK(OnWindowDestroyedThunk), this);
vbox_ = gtk_vbox_new(FALSE, 0);
// Create the menu bar.
GtkWidget* menu_bar = CreateMenuBar(this);
gtk_box_pack_start(GTK_BOX(vbox_), menu_bar, FALSE, FALSE, 0);
// Create the object that mediates accelerators.
GtkAccelGroup* accel_group = gtk_accel_group_new();
gtk_window_add_accel_group(GTK_WINDOW(window_), accel_group);
// Set global window handling accelerators:
gtk_accel_group_connect(
accel_group, GDK_w, GDK_CONTROL_MASK,
GTK_ACCEL_VISIBLE,
g_cclosure_new(G_CALLBACK(OnCloseWindowKeyPressedThunk),
this, NULL));
gtk_accel_group_connect(
accel_group, GDK_n, GDK_CONTROL_MASK,
GTK_ACCEL_VISIBLE,
g_cclosure_new(G_CALLBACK(OnNewWindowKeyPressedThunk),
this, NULL));
gtk_accel_group_connect(
accel_group, GDK_F5, (GdkModifierType)0,
GTK_ACCEL_VISIBLE,
g_cclosure_new(G_CALLBACK(OnReloadKeyPressedThunk),
this, NULL));
GtkWidget* toolbar = gtk_toolbar_new();
// Turn off the labels on the toolbar buttons.
gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);
back_button_ = gtk_tool_button_new_from_stock(GTK_STOCK_GO_BACK);
g_signal_connect(back_button_, "clicked",
G_CALLBACK(&OnBackButtonClickedThunk), this);
gtk_toolbar_insert(GTK_TOOLBAR(toolbar), back_button_, -1 /* append */);
gtk_widget_add_accelerator(GTK_WIDGET(back_button_), "clicked", accel_group,
GDK_Left, GDK_MOD1_MASK, GTK_ACCEL_VISIBLE);
forward_button_ = gtk_tool_button_new_from_stock(GTK_STOCK_GO_FORWARD);
g_signal_connect(forward_button_, "clicked",
G_CALLBACK(&OnForwardButtonClickedThunk), this);
gtk_toolbar_insert(GTK_TOOLBAR(toolbar), forward_button_, -1 /* append */);
gtk_widget_add_accelerator(GTK_WIDGET(forward_button_), "clicked",
accel_group,
GDK_Right, GDK_MOD1_MASK, GTK_ACCEL_VISIBLE);
reload_button_ = gtk_tool_button_new_from_stock(GTK_STOCK_REFRESH);
g_signal_connect(reload_button_, "clicked",
G_CALLBACK(&OnReloadButtonClickedThunk), this);
gtk_toolbar_insert(GTK_TOOLBAR(toolbar), reload_button_, -1 /* append */);
gtk_widget_add_accelerator(GTK_WIDGET(reload_button_), "clicked",
accel_group,
GDK_r, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE);
stop_button_ = gtk_tool_button_new_from_stock(GTK_STOCK_STOP);
g_signal_connect(stop_button_, "clicked",
G_CALLBACK(&OnStopButtonClickedThunk), this);
gtk_toolbar_insert(GTK_TOOLBAR(toolbar), stop_button_, -1 /* append */);
url_edit_view_ = gtk_entry_new();
g_signal_connect(G_OBJECT(url_edit_view_), "activate",
G_CALLBACK(&OnURLEntryActivateThunk), this);
gtk_accel_group_connect(
accel_group, GDK_l, GDK_CONTROL_MASK,
GTK_ACCEL_VISIBLE,
g_cclosure_new(G_CALLBACK(OnHighlightURLViewThunk),
this, NULL));
GtkToolItem* tool_item = gtk_tool_item_new();
gtk_container_add(GTK_CONTAINER(tool_item), url_edit_view_);
gtk_tool_item_set_expand(tool_item, TRUE);
gtk_toolbar_insert(GTK_TOOLBAR(toolbar), tool_item, -1 /* append */);
// Center a 20x20 spinner in a 26x24 area.
GtkWidget* spinner_alignment = gtk_alignment_new(0.5, 0.5, 0, 0);
gtk_alignment_set_padding(GTK_ALIGNMENT(spinner_alignment), 2, 2, 4, 4);
spinner_ = gtk_spinner_new();
gtk_widget_set_size_request(spinner_, 20, 20);
gtk_container_add(GTK_CONTAINER(spinner_alignment), spinner_);
spinner_item_ = gtk_tool_item_new();
gtk_container_add(GTK_CONTAINER(spinner_item_), spinner_alignment);
gtk_toolbar_insert(GTK_TOOLBAR(toolbar), spinner_item_, -1 /* append */);
gtk_box_pack_start(GTK_BOX(vbox_), toolbar, FALSE, FALSE, 0);
gtk_container_add(GTK_CONTAINER(window_), vbox_);
// Trigger layout of the UI elements, so that we can measure their
// heights. The width and height passed to this method are meant for the web
// contents view, not the top-level window. Since Gtk only seems to provide a
// suitable resizing function for top-level windows, we need to know how to
// convert from web contents view size to top-level window size.
gtk_widget_show_all(GTK_WIDGET(vbox_));
// Measure the heights of the UI elements, now that they have been laid out.
GtkRequisition elm_size;
gtk_widget_size_request(menu_bar, &elm_size);
ui_elements_height_ += elm_size.height;
gtk_widget_size_request(toolbar, &elm_size);
ui_elements_height_ += elm_size.height;
// We're ready to set an initial window size.
SizeTo(gfx::Size(width, height));
// Finally, show the window.
gtk_widget_show_all(GTK_WIDGET(window_));
}
void Shell::PlatformSetContents() {
if (headless_) {
SizeTo(content_size_);
return;
}
WebContentsView* content_view = web_contents_->GetView();
gtk_container_add(GTK_CONTAINER(vbox_), content_view->GetNativeView());
}
void Shell::SizeTo(const gfx::Size& content_size) {
content_size_ = content_size;
if (window_) {
gtk_window_resize(window_,
content_size.width(),
content_size.height() + ui_elements_height_);
} else if (web_contents_) {
RenderWidgetHostView* render_widget_host_view =
web_contents_->GetRenderWidgetHostView();
if (render_widget_host_view)
render_widget_host_view->SetSize(content_size);
}
}
void Shell::PlatformResizeSubViews() {
// Not needed; the subviews are bound.
}
void Shell::Close() {
if (headless_) {
delete this;
return;
}
gtk_widget_destroy(GTK_WIDGET(window_));
}
void Shell::OnBackButtonClicked(GtkWidget* widget) {
GoBackOrForward(-1);
}
void Shell::OnForwardButtonClicked(GtkWidget* widget) {
GoBackOrForward(1);
}
void Shell::OnReloadButtonClicked(GtkWidget* widget) {
Reload();
}
void Shell::OnStopButtonClicked(GtkWidget* widget) {
Stop();
}
void Shell::OnURLEntryActivate(GtkWidget* entry) {
const gchar* str = gtk_entry_get_text(GTK_ENTRY(entry));
GURL url(str);
if (!url.has_scheme())
url = GURL(std::string("http://") + std::string(str));
if (url.is_valid())
LoadURL(url);
}
// Callback for when the main window is destroyed.
gboolean Shell::OnWindowDestroyed(GtkWidget* window) {
delete this;
return FALSE; // Don't stop this message.
}
gboolean Shell::OnCloseWindowKeyPressed(GtkAccelGroup* accel_group,
GObject* acceleratable,
guint keyval,
GdkModifierType modifier) {
gtk_widget_destroy(GTK_WIDGET(window_));
return TRUE;
}
gboolean Shell::OnNewWindowKeyPressed(GtkAccelGroup* accel_group,
GObject* acceleratable,
guint keyval,
GdkModifierType modifier) {
ShellBrowserContext* browser_context =
ShellContentBrowserClient::Get()->browser_context();
Shell::CreateNewWindow(browser_context,
GURL(),
NULL,
MSG_ROUTING_NONE,
gfx::Size());
return TRUE;
}
gboolean Shell::OnHighlightURLView(GtkAccelGroup* accel_group,
GObject* acceleratable,
guint keyval,
GdkModifierType modifier) {
gtk_widget_grab_focus(GTK_WIDGET(url_edit_view_));
return TRUE;
}
gboolean Shell::OnReloadKeyPressed(GtkAccelGroup* accel_group,
GObject* acceleratable,
guint keyval,
GdkModifierType modifier) {
Reload();
return TRUE;
}
void Shell::PlatformSetTitle(const base::string16& title) {
if (headless_)
return;
std::string title_utf8 = UTF16ToUTF8(title);
gtk_window_set_title(GTK_WINDOW(window_), title_utf8.c_str());
}
} // namespace content