summaryrefslogtreecommitdiffstats
path: root/chrome/browser/gtk/browser_window_gtk.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/gtk/browser_window_gtk.cc')
-rw-r--r--chrome/browser/gtk/browser_window_gtk.cc2155
1 files changed, 2155 insertions, 0 deletions
diff --git a/chrome/browser/gtk/browser_window_gtk.cc b/chrome/browser/gtk/browser_window_gtk.cc
new file mode 100644
index 0000000..4d8cf72
--- /dev/null
+++ b/chrome/browser/gtk/browser_window_gtk.cc
@@ -0,0 +1,2155 @@
+// Copyright (c) 2010 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/gtk/browser_window_gtk.h"
+
+#include <gdk/gdkkeysyms.h>
+
+#include <string>
+
+#include "app/gtk_util.h"
+#include "app/l10n_util.h"
+#include "app/resource_bundle.h"
+#include "app/theme_provider.h"
+#include "base/base_paths.h"
+#include "base/command_line.h"
+#include "base/keyboard_codes.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
+#include "base/scoped_ptr.h"
+#include "base/singleton.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "chrome/app/chrome_dll_resource.h"
+#include "chrome/browser/app_modal_dialog_queue.h"
+#include "chrome/browser/autocomplete/autocomplete_edit_view.h"
+#include "chrome/browser/bookmarks/bookmark_utils.h"
+#include "chrome/browser/browser.h"
+#include "chrome/browser/browser_list.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/browser_theme_provider.h"
+#include "chrome/browser/debugger/devtools_window.h"
+#include "chrome/browser/download/download_item_model.h"
+#include "chrome/browser/download/download_manager.h"
+#include "chrome/browser/find_bar_controller.h"
+#include "chrome/browser/gtk/about_chrome_dialog.h"
+#include "chrome/browser/gtk/accelerators_gtk.h"
+#include "chrome/browser/gtk/bookmark_bar_gtk.h"
+#include "chrome/browser/gtk/browser_titlebar.h"
+#include "chrome/browser/gtk/browser_toolbar_gtk.h"
+#include "chrome/browser/gtk/cairo_cached_surface.h"
+#include "chrome/browser/gtk/clear_browsing_data_dialog_gtk.h"
+#include "chrome/browser/gtk/collected_cookies_gtk.h"
+#include "chrome/browser/gtk/create_application_shortcuts_dialog_gtk.h"
+#include "chrome/browser/gtk/download_in_progress_dialog_gtk.h"
+#include "chrome/browser/gtk/download_shelf_gtk.h"
+#include "chrome/browser/gtk/edit_search_engine_dialog.h"
+#include "chrome/browser/gtk/find_bar_gtk.h"
+#include "chrome/browser/gtk/fullscreen_exit_bubble_gtk.h"
+#include "chrome/browser/gtk/gtk_floating_container.h"
+#include "chrome/browser/gtk/gtk_theme_provider.h"
+#include "chrome/browser/gtk/gtk_util.h"
+#include "chrome/browser/gtk/html_dialog_gtk.h"
+#include "chrome/browser/gtk/import_dialog_gtk.h"
+#include "chrome/browser/gtk/info_bubble_gtk.h"
+#include "chrome/browser/gtk/infobar_container_gtk.h"
+#include "chrome/browser/gtk/keyword_editor_view.h"
+#include "chrome/browser/gtk/location_bar_view_gtk.h"
+#include "chrome/browser/gtk/nine_box.h"
+#include "chrome/browser/gtk/options/content_settings_window_gtk.h"
+#include "chrome/browser/gtk/reload_button_gtk.h"
+#include "chrome/browser/gtk/repost_form_warning_gtk.h"
+#include "chrome/browser/gtk/status_bubble_gtk.h"
+#include "chrome/browser/gtk/tab_contents_container_gtk.h"
+#include "chrome/browser/gtk/tabs/tab_strip_gtk.h"
+#include "chrome/browser/gtk/task_manager_gtk.h"
+#include "chrome/browser/gtk/theme_install_bubble_view_gtk.h"
+#include "chrome/browser/gtk/update_recommended_dialog.h"
+#include "chrome/browser/location_bar.h"
+#include "chrome/browser/page_info_window.h"
+#include "chrome/browser/pref_service.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/renderer_host/render_widget_host_view_gtk.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/browser/tab_contents/tab_contents_view.h"
+#include "chrome/browser/window_sizer.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/pref_names.h"
+#include "gfx/color_utils.h"
+#include "gfx/gtk_util.h"
+#include "gfx/rect.h"
+#include "gfx/skia_utils_gtk.h"
+#include "grit/app_resources.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "grit/theme_resources.h"
+
+namespace {
+
+// The number of milliseconds between loading animation frames.
+const int kLoadingAnimationFrameTimeMs = 30;
+
+// Default height of dev tools pane when docked to the browser window. This
+// matches the value in Views.
+const int kDefaultDevToolsHeight = 200;
+
+const char* kBrowserWindowKey = "__BROWSER_WINDOW_GTK__";
+
+// The frame border is only visible in restored mode and is hardcoded to 4 px
+// on each side regardless of the system window border size.
+const int kFrameBorderThickness = 4;
+// While resize areas on Windows are normally the same size as the window
+// borders, our top area is shrunk by 1 px to make it easier to move the window
+// around with our thinner top grabbable strip. (Incidentally, our side and
+// bottom resize areas don't match the frame border thickness either -- they
+// span the whole nonclient area, so there's no "dead zone" for the mouse.)
+const int kTopResizeAdjust = 1;
+// In the window corners, the resize areas don't actually expand bigger, but
+// the 16 px at the end of each edge triggers diagonal resizing.
+const int kResizeAreaCornerSize = 16;
+// The thickness of the shadow around the toolbar+web content area. There are
+// actually a couple pixels more that should overlap the toolbar and web
+// content area, but we don't use those pixels.
+const int kContentShadowThickness = 2;
+// The offset to the background when the custom frame is off. We want the
+// window background to line up with the tab background regardless of whether
+// we're in custom frame mode or not. Since themes are designed with the
+// custom frame in mind, we need to offset the background when the custom frame
+// is off.
+const int kCustomFrameBackgroundVerticalOffset = 15;
+
+// The timeout in milliseconds before we'll get the true window position with
+// gtk_window_get_position() after the last GTK configure-event signal.
+const int kDebounceTimeoutMilliseconds = 100;
+
+gboolean MainWindowConfigured(GtkWindow* window, GdkEventConfigure* event,
+ BrowserWindowGtk* browser_win) {
+ gfx::Rect bounds = gfx::Rect(event->x, event->y, event->width, event->height);
+ browser_win->OnBoundsChanged(bounds);
+ return FALSE;
+}
+
+gboolean MainWindowStateChanged(GtkWindow* window, GdkEventWindowState* event,
+ BrowserWindowGtk* browser_win) {
+ browser_win->OnStateChanged(event->new_window_state, event->changed_mask);
+ return FALSE;
+}
+
+// Callback for the delete event. This event is fired when the user tries to
+// close the window (e.g., clicking on the X in the window manager title bar).
+gboolean MainWindowDeleteEvent(GtkWidget* widget, GdkEvent* event,
+ BrowserWindowGtk* window) {
+ window->Close();
+
+ // Return true to prevent the gtk window from being destroyed. Close will
+ // destroy it for us.
+ return TRUE;
+}
+
+void MainWindowDestroy(GtkWidget* widget, BrowserWindowGtk* window) {
+ // BUG 8712. When we gtk_widget_destroy() in Close(), this will emit the
+ // signal right away, and we will be here (while Close() is still in the
+ // call stack). In order to not reenter Close(), and to also follow the
+ // expectations of BrowserList, we should run the BrowserWindowGtk destructor
+ // not now, but after the run loop goes back to process messages. Otherwise
+ // we will remove ourself from BrowserList while it's being iterated.
+ // Additionally, now that we know the window is gone, we need to make sure to
+ // set window_ to NULL, otherwise we will try to close the window again when
+ // we call Close() in the destructor.
+ //
+ // We don't want to use DeleteSoon() here since it won't work on a nested pump
+ // (like in UI tests).
+ MessageLoop::current()->PostTask(FROM_HERE,
+ new DeleteTask<BrowserWindowGtk>(window));
+}
+
+// Using gtk_window_get_position/size creates a race condition, so only use
+// this to get the initial bounds. After window creation, we pick up the
+// normal bounds by connecting to the configure-event signal.
+gfx::Rect GetInitialWindowBounds(GtkWindow* window) {
+ gint x, y, width, height;
+ gtk_window_get_position(window, &x, &y);
+ gtk_window_get_size(window, &width, &height);
+ return gfx::Rect(x, y, width, height);
+}
+
+// Get the command ids of the key combinations that are not valid gtk
+// accelerators.
+int GetCustomCommandId(GdkEventKey* event) {
+ // Filter modifier to only include accelerator modifiers.
+ guint modifier = event->state & gtk_accelerator_get_default_mod_mask();
+ switch (event->keyval) {
+ // Gtk doesn't allow GDK_Tab or GDK_ISO_Left_Tab to be an accelerator (see
+ // gtk_accelerator_valid), so we need to handle these accelerators
+ // manually.
+ // Some X clients (e.g. cygwin, NX client, etc.) also send GDK_KP_Tab when
+ // typing a tab key. We should also handle GDK_KP_Tab for such X clients as
+ // Firefox does.
+ case GDK_Tab:
+ case GDK_ISO_Left_Tab:
+ case GDK_KP_Tab:
+ if (GDK_CONTROL_MASK == modifier) {
+ return IDC_SELECT_NEXT_TAB;
+ } else if ((GDK_CONTROL_MASK | GDK_SHIFT_MASK) == modifier) {
+ return IDC_SELECT_PREVIOUS_TAB;
+ }
+ break;
+
+ default:
+ break;
+ }
+ return -1;
+}
+
+// Get the command ids of the accelerators that we don't want the native widget
+// to be able to override.
+int GetPreHandleCommandId(GdkEventKey* event) {
+ // Filter modifier to only include accelerator modifiers.
+ guint modifier = event->state & gtk_accelerator_get_default_mod_mask();
+ switch (event->keyval) {
+ case GDK_Page_Down:
+ if (GDK_CONTROL_MASK == modifier) {
+ return IDC_SELECT_NEXT_TAB;
+ } else if ((GDK_CONTROL_MASK | GDK_SHIFT_MASK) == modifier) {
+ return IDC_MOVE_TAB_NEXT;
+ }
+ break;
+
+ case GDK_Page_Up:
+ if (GDK_CONTROL_MASK == modifier) {
+ return IDC_SELECT_PREVIOUS_TAB;
+ } else if ((GDK_CONTROL_MASK | GDK_SHIFT_MASK) == modifier) {
+ return IDC_MOVE_TAB_PREVIOUS;
+ }
+ break;
+
+ default:
+ break;
+ }
+ return -1;
+}
+
+GdkCursorType GdkWindowEdgeToGdkCursorType(GdkWindowEdge edge) {
+ switch (edge) {
+ case GDK_WINDOW_EDGE_NORTH_WEST:
+ return GDK_TOP_LEFT_CORNER;
+ case GDK_WINDOW_EDGE_NORTH:
+ return GDK_TOP_SIDE;
+ case GDK_WINDOW_EDGE_NORTH_EAST:
+ return GDK_TOP_RIGHT_CORNER;
+ case GDK_WINDOW_EDGE_WEST:
+ return GDK_LEFT_SIDE;
+ case GDK_WINDOW_EDGE_EAST:
+ return GDK_RIGHT_SIDE;
+ case GDK_WINDOW_EDGE_SOUTH_WEST:
+ return GDK_BOTTOM_LEFT_CORNER;
+ case GDK_WINDOW_EDGE_SOUTH:
+ return GDK_BOTTOM_SIDE;
+ case GDK_WINDOW_EDGE_SOUTH_EAST:
+ return GDK_BOTTOM_RIGHT_CORNER;
+ default:
+ NOTREACHED();
+ }
+ return GDK_LAST_CURSOR;
+}
+
+GdkColor SkColorToGdkColor(const SkColor& color) {
+ return gfx::SkColorToGdkColor(color);
+}
+
+// A helper method for setting the GtkWindow size that should be used in place
+// of calling gtk_window_resize directly. This is done to avoid a WM "feature"
+// where setting the window size to the screen size causes the WM to set the
+// EWMH for full screen mode.
+void SetWindowSize(GtkWindow* window, int width, int height) {
+ GdkScreen* screen = gtk_window_get_screen(window);
+ if (width >= gdk_screen_get_width(screen) &&
+ height >= gdk_screen_get_height(screen)) {
+ // Adjust the height so we don't trigger the WM feature.
+ gtk_window_resize(window, width, height - 1);
+ } else {
+ gtk_window_resize(window, width, height);
+ }
+}
+
+GQuark GetBrowserWindowQuarkKey() {
+ static GQuark quark = g_quark_from_static_string(kBrowserWindowKey);
+ return quark;
+}
+
+// Checks if a reserved accelerator key should be processed immediately, rather
+// than being sent to the renderer first.
+bool ShouldExecuteReservedCommandImmediately(
+ const NativeWebKeyboardEvent& event, int command_id) {
+ // IDC_EXIT is now only bound to Ctrl+Shift+q, so we should always execute it
+ // immediately.
+ if (command_id == IDC_EXIT)
+ return true;
+
+ // Keys like Ctrl+w, Ctrl+n, etc. should always be sent to the renderer first,
+ // otherwise some web apps or the Emacs key bindings may not work correctly.
+ int vkey = event.windowsKeyCode;
+ if ((vkey >= base::VKEY_0 && vkey <= base::VKEY_9) ||
+ (vkey >= base::VKEY_A && vkey <= base::VKEY_Z))
+ return false;
+
+ // All other reserved accelerators should be processed immediately.
+ return true;
+}
+
+// Performs Cut/Copy/Paste operation on the |window|.
+// If the current render view is focused, then just call the specified |method|
+// against the current render view host, otherwise emit the specified |signal|
+// against the focused widget.
+// TODO(suzhe): This approach does not work for plugins.
+void DoCutCopyPaste(BrowserWindowGtk* window, void (RenderViewHost::*method)(),
+ const char* signal) {
+ TabContents* current_tab_contents =
+ window->browser()->tabstrip_model()->GetSelectedTabContents();
+ if (current_tab_contents && current_tab_contents->GetContentNativeView() &&
+ gtk_widget_is_focus(current_tab_contents->GetContentNativeView())) {
+ (current_tab_contents->render_view_host()->*method)();
+ } else {
+ GtkWidget* widget = gtk_window_get_focus(window->window());
+ guint id;
+ if (widget && (id = g_signal_lookup(signal, G_OBJECT_TYPE(widget))) != 0)
+ g_signal_emit(widget, id, 0);
+ }
+}
+
+} // namespace
+
+std::map<XID, GtkWindow*> BrowserWindowGtk::xid_map_;
+
+BrowserWindowGtk::BrowserWindowGtk(Browser* browser)
+ : browser_(browser),
+ state_(GDK_WINDOW_STATE_WITHDRAWN),
+ frame_cursor_(NULL),
+ is_active_(true),
+ last_click_time_(0),
+ maximize_after_show_(false),
+ suppress_window_raise_(false),
+ accel_group_(NULL) {
+ // We register first so that other views like the toolbar can use the
+ // is_active() function in their ActiveWindowChanged() handlers.
+ ActiveWindowWatcherX::AddObserver(this);
+
+ use_custom_frame_pref_.Init(prefs::kUseCustomChromeFrame,
+ browser_->profile()->GetPrefs(), this);
+
+ // In some (older) versions of compiz, raising top-level windows when they
+ // are partially off-screen causes them to get snapped back on screen, not
+ // always even on the current virtual desktop. If we are running under
+ // compiz, suppress such raises, as they are not necessary in compiz anyway.
+ std::string wm_name;
+ if (x11_util::GetWindowManagerName(&wm_name) && wm_name == "compiz")
+ suppress_window_raise_ = true;
+
+ window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
+ g_object_set_qdata(G_OBJECT(window_), GetBrowserWindowQuarkKey(), this);
+ gtk_widget_add_events(GTK_WIDGET(window_), GDK_BUTTON_PRESS_MASK |
+ GDK_POINTER_MOTION_MASK);
+
+ // Add this window to its own unique window group to allow for
+ // window-to-parent modality.
+ gtk_window_group_add_window(gtk_window_group_new(), window_);
+ g_object_unref(gtk_window_get_group(window_));
+
+ // For popups, we initialize widgets then set the window geometry, because
+ // popups need the widgets inited before they can set the window size
+ // properly. For other windows, we set the geometry first to prevent resize
+ // flicker.
+ if (browser_->type() & Browser::TYPE_POPUP) {
+ InitWidgets();
+ SetGeometryHints();
+ } else {
+ SetGeometryHints();
+ InitWidgets();
+ }
+
+ ConnectAccelerators();
+
+ // Set the initial background color of widgets.
+ SetBackgroundColor();
+ HideUnsupportedWindowFeatures();
+
+ registrar_.Add(this, NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED,
+ NotificationService::AllSources());
+}
+
+BrowserWindowGtk::~BrowserWindowGtk() {
+ ActiveWindowWatcherX::RemoveObserver(this);
+
+ browser_->tabstrip_model()->RemoveObserver(this);
+
+ if (frame_cursor_) {
+ gdk_cursor_unref(frame_cursor_);
+ frame_cursor_ = NULL;
+ }
+}
+
+gboolean BrowserWindowGtk::OnCustomFrameExpose(GtkWidget* widget,
+ GdkEventExpose* event) {
+ // Draw the default background.
+ cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(widget->window));
+ gdk_cairo_rectangle(cr, &event->area);
+ cairo_clip(cr);
+
+ if (UsingCustomPopupFrame()) {
+ DrawPopupFrame(cr, widget, event);
+ } else {
+ DrawCustomFrame(cr, widget, event);
+ }
+
+ DrawContentShadow(cr);
+
+ cairo_destroy(cr);
+
+ if (UseCustomFrame() && !IsMaximized()) {
+ static NineBox custom_frame_border(
+ IDR_WINDOW_TOP_LEFT_CORNER,
+ IDR_WINDOW_TOP_CENTER,
+ IDR_WINDOW_TOP_RIGHT_CORNER,
+ IDR_WINDOW_LEFT_SIDE,
+ 0,
+ IDR_WINDOW_RIGHT_SIDE,
+ IDR_WINDOW_BOTTOM_LEFT_CORNER,
+ IDR_WINDOW_BOTTOM_CENTER,
+ IDR_WINDOW_BOTTOM_RIGHT_CORNER);
+
+ custom_frame_border.RenderToWidget(widget);
+ }
+
+ return FALSE; // Allow subwidgets to paint.
+}
+
+void BrowserWindowGtk::DrawContentShadow(cairo_t* cr) {
+ // Draw the shadow above the toolbar. Tabs on the tabstrip will draw over us.
+ GtkThemeProvider* theme_provider = GtkThemeProvider::GetFrom(
+ browser()->profile());
+ int left_x, top_y;
+ gtk_widget_translate_coordinates(toolbar_->widget(),
+ GTK_WIDGET(window_), 0, 0, &left_x,
+ &top_y);
+ int center_width = window_vbox_->allocation.width;
+
+ CairoCachedSurface* top_center = theme_provider->GetSurfaceNamed(
+ IDR_CONTENT_TOP_CENTER, GTK_WIDGET(window_));
+ CairoCachedSurface* top_right = theme_provider->GetSurfaceNamed(
+ IDR_CONTENT_TOP_RIGHT_CORNER, GTK_WIDGET(window_));
+ CairoCachedSurface* top_left = theme_provider->GetSurfaceNamed(
+ IDR_CONTENT_TOP_LEFT_CORNER, GTK_WIDGET(window_));
+
+ int center_left_x = left_x;
+ if (ShouldDrawContentDropShadow()) {
+ // Don't draw over the corners.
+ center_left_x += top_left->Width() - kContentShadowThickness;
+ center_width -= (top_left->Width() + top_right->Width());
+ center_width += 2 * kContentShadowThickness;
+ }
+
+ top_center->SetSource(cr, center_left_x, top_y - kContentShadowThickness);
+ cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
+ cairo_rectangle(cr, center_left_x, top_y - kContentShadowThickness,
+ center_width, top_center->Height());
+ cairo_fill(cr);
+
+ // Only draw the rest of the shadow if the user has the custom frame enabled
+ // and the browser is not maximized.
+ if (!ShouldDrawContentDropShadow())
+ return;
+
+ // The top left corner has a width of 3 pixels. On Windows, the last column
+ // of pixels overlap the toolbar. We just crop it off on Linux. The top
+ // corners extend to the base of the toolbar (one pixel above the dividing
+ // line).
+ int right_x = center_left_x + center_width;
+ top_left->SetSource(
+ cr, left_x - kContentShadowThickness, top_y - kContentShadowThickness);
+ // The toolbar is shorter in location bar only mode so clip the image to the
+ // height of the toolbar + the amount of shadow above the toolbar.
+ cairo_rectangle(cr,
+ left_x - kContentShadowThickness,
+ top_y - kContentShadowThickness,
+ top_left->Width(),
+ top_left->Height());
+ cairo_fill(cr);
+
+ // Likewise, we crop off the left column of pixels for the top right corner.
+ top_right->SetSource(cr, right_x, top_y - kContentShadowThickness);
+ cairo_rectangle(cr,
+ right_x,
+ top_y - kContentShadowThickness,
+ top_right->Width(),
+ top_right->Height());
+ cairo_fill(cr);
+
+ // Fill in the sides. As above, we only draw 2 of the 3 columns on Linux.
+ int bottom_y;
+ gtk_widget_translate_coordinates(window_vbox_,
+ GTK_WIDGET(window_),
+ 0, window_vbox_->allocation.height,
+ NULL, &bottom_y);
+ // |side_y| is where to start drawing the side shadows. The top corners draw
+ // the sides down to the bottom of the toolbar.
+ int side_y = top_y - kContentShadowThickness + top_right->Height();
+ // |side_height| is how many pixels to draw for the side borders. We do one
+ // pixel before the bottom of the web contents because that extra pixel is
+ // drawn by the bottom corners.
+ int side_height = bottom_y - side_y - 1;
+ if (side_height > 0) {
+ CairoCachedSurface* left = theme_provider->GetSurfaceNamed(
+ IDR_CONTENT_LEFT_SIDE, GTK_WIDGET(window_));
+ left->SetSource(cr, left_x - kContentShadowThickness, side_y);
+ cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
+ cairo_rectangle(cr,
+ left_x - kContentShadowThickness,
+ side_y,
+ kContentShadowThickness,
+ side_height);
+ cairo_fill(cr);
+
+ CairoCachedSurface* right = theme_provider->GetSurfaceNamed(
+ IDR_CONTENT_RIGHT_SIDE, GTK_WIDGET(window_));
+ int right_side_x =
+ right_x + top_right->Width() - kContentShadowThickness - 1;
+ right->SetSource(cr, right_side_x, side_y);
+ cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
+ cairo_rectangle(cr,
+ right_side_x,
+ side_y,
+ kContentShadowThickness,
+ side_height);
+ cairo_fill(cr);
+ }
+
+ // Draw the bottom corners. The bottom corners also draw the bottom row of
+ // pixels of the side shadows.
+ CairoCachedSurface* bottom_left = theme_provider->GetSurfaceNamed(
+ IDR_CONTENT_BOTTOM_LEFT_CORNER, GTK_WIDGET(window_));
+ bottom_left->SetSource(cr, left_x - kContentShadowThickness, bottom_y - 1);
+ cairo_paint(cr);
+
+ CairoCachedSurface* bottom_right = theme_provider->GetSurfaceNamed(
+ IDR_CONTENT_BOTTOM_RIGHT_CORNER, GTK_WIDGET(window_));
+ bottom_right->SetSource(cr, right_x - 1, bottom_y - 1);
+ cairo_paint(cr);
+
+ // Finally, draw the bottom row. Since we don't overlap the contents, we clip
+ // the top row of pixels.
+ CairoCachedSurface* bottom = theme_provider->GetSurfaceNamed(
+ IDR_CONTENT_BOTTOM_CENTER, GTK_WIDGET(window_));
+ bottom->SetSource(cr, left_x + 1, bottom_y - 1);
+ cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
+ cairo_rectangle(cr,
+ left_x + 1,
+ bottom_y,
+ window_vbox_->allocation.width - 2,
+ kContentShadowThickness);
+ cairo_fill(cr);
+}
+
+void BrowserWindowGtk::DrawPopupFrame(cairo_t* cr,
+ GtkWidget* widget,
+ GdkEventExpose* event) {
+ GtkThemeProvider* theme_provider = GtkThemeProvider::GetFrom(
+ browser()->profile());
+
+ // Like DrawCustomFrame(), except that we use the unthemed resources to draw
+ // the background. We do this because we can't rely on sane images in the
+ // theme that we can draw text on. (We tried using the tab background, but
+ // that has inverse saturation from what the user usually expects).
+ int image_name = GetThemeFrameResource();
+ CairoCachedSurface* surface = theme_provider->GetUnthemedSurfaceNamed(
+ image_name, widget);
+ surface->SetSource(
+ cr, 0, UseCustomFrame() ? 0 : -kCustomFrameBackgroundVerticalOffset);
+ cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REFLECT);
+ cairo_rectangle(cr, event->area.x, event->area.y,
+ event->area.width, event->area.height);
+ cairo_fill(cr);
+}
+
+void BrowserWindowGtk::DrawCustomFrame(cairo_t* cr,
+ GtkWidget* widget,
+ GdkEventExpose* event) {
+ GtkThemeProvider* theme_provider = GtkThemeProvider::GetFrom(
+ browser()->profile());
+
+ int image_name = GetThemeFrameResource();
+
+ CairoCachedSurface* surface = theme_provider->GetSurfaceNamed(
+ image_name, widget);
+ if (event->area.y < surface->Height()) {
+ surface->SetSource(cr,
+ 0,
+ UseCustomFrame() ? 0 : -kCustomFrameBackgroundVerticalOffset);
+ // The frame background isn't tiled vertically.
+ cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
+ cairo_rectangle(cr, event->area.x, event->area.y,
+ event->area.width, surface->Height() - event->area.y);
+ cairo_fill(cr);
+ }
+
+ if (theme_provider->HasCustomImage(IDR_THEME_FRAME_OVERLAY) &&
+ !browser()->profile()->IsOffTheRecord()) {
+ CairoCachedSurface* theme_overlay = theme_provider->GetSurfaceNamed(
+ IsActive() ? IDR_THEME_FRAME_OVERLAY
+ : IDR_THEME_FRAME_OVERLAY_INACTIVE, widget);
+ theme_overlay->SetSource(cr, 0, 0);
+ cairo_paint(cr);
+ }
+}
+
+int BrowserWindowGtk::GetThemeFrameResource() {
+ bool off_the_record = browser()->profile()->IsOffTheRecord();
+ int image_name;
+ if (IsActive()) {
+ image_name = off_the_record ? IDR_THEME_FRAME_INCOGNITO : IDR_THEME_FRAME;
+ } else {
+ image_name = off_the_record ? IDR_THEME_FRAME_INCOGNITO_INACTIVE :
+ IDR_THEME_FRAME_INACTIVE;
+ }
+
+ return image_name;
+}
+
+void BrowserWindowGtk::Show() {
+ // The Browser associated with this browser window must become the active
+ // browser at the time Show() is called. This is the natural behaviour under
+ // Windows, but gtk_widget_show won't show the widget (and therefore won't
+ // call OnFocusIn()) until we return to the runloop. Therefore any calls to
+ // BrowserList::GetLastActive() (for example, in bookmark_util), will return
+ // the previous browser instead if we don't explicitly set it here.
+ BrowserList::SetLastActive(browser());
+
+ gtk_window_present(window_);
+ if (maximize_after_show_) {
+ gtk_window_maximize(window_);
+ maximize_after_show_ = false;
+ }
+
+ // If we have sized the window by setting a size request for the render
+ // area, then undo it so that the render view can later adjust its own
+ // size.
+ gtk_widget_set_size_request(contents_container_->widget(), -1, -1);
+}
+
+void BrowserWindowGtk::SetBoundsImpl(const gfx::Rect& bounds, bool exterior) {
+ gint x = static_cast<gint>(bounds.x());
+ gint y = static_cast<gint>(bounds.y());
+ gint width = static_cast<gint>(bounds.width());
+ gint height = static_cast<gint>(bounds.height());
+
+ gtk_window_move(window_, x, y);
+
+ if (exterior) {
+ SetWindowSize(window_, width, height);
+ } else {
+ gtk_widget_set_size_request(contents_container_->widget(),
+ width, height);
+ }
+}
+
+void BrowserWindowGtk::SetBounds(const gfx::Rect& bounds) {
+ SetBoundsImpl(bounds, true);
+}
+
+void BrowserWindowGtk::Close() {
+ // We're already closing. Do nothing.
+ if (!window_)
+ return;
+
+ // Sometimes the tabstrip will get stuck thinking it's in a drag session.
+ // Short of figuring out a repro case and actually solving the problem, this
+ // is the best way to avoid an immortal window. See http://crbug.com/23733
+ tabstrip_->CancelActiveDragSession();
+
+ if (!CanClose())
+ return;
+
+ // We're going to destroy the window, make sure the tab strip isn't running
+ // any animations which may still reference GtkWidgets.
+ tabstrip_->StopAnimation();
+
+ SaveWindowPosition();
+
+ if (accel_group_) {
+ // Disconnecting the keys we connected to our accelerator group frees the
+ // closures allocated in ConnectAccelerators.
+ AcceleratorsGtk* accelerators = Singleton<AcceleratorsGtk>().get();
+ for (AcceleratorsGtk::const_iterator iter = accelerators->begin();
+ iter != accelerators->end(); ++iter) {
+ gtk_accel_group_disconnect_key(accel_group_,
+ iter->second.GetGdkKeyCode(),
+ static_cast<GdkModifierType>(iter->second.modifiers()));
+ }
+ gtk_window_remove_accel_group(window_, accel_group_);
+ g_object_unref(accel_group_);
+ accel_group_ = NULL;
+ }
+
+ // Cancel any pending callback from the window configure debounce timer.
+ window_configure_debounce_timer_.Stop();
+
+ // Likewise for the loading animation.
+ loading_animation_timer_.Stop();
+
+ GtkWidget* window = GTK_WIDGET(window_);
+ // To help catch bugs in any event handlers that might get fired during the
+ // destruction, set window_ to NULL before any handlers will run.
+ window_ = NULL;
+ titlebar_->set_window(NULL);
+ gtk_widget_destroy(window);
+}
+
+void BrowserWindowGtk::Activate() {
+ gtk_window_present(window_);
+}
+
+bool BrowserWindowGtk::IsActive() const {
+ return is_active_;
+}
+
+void BrowserWindowGtk::FlashFrame() {
+ // May not be respected by all window managers.
+ gtk_window_set_urgency_hint(window_, TRUE);
+}
+
+gfx::NativeWindow BrowserWindowGtk::GetNativeHandle() {
+ return window_;
+}
+
+BrowserWindowTesting* BrowserWindowGtk::GetBrowserWindowTesting() {
+ NOTIMPLEMENTED();
+ return NULL;
+}
+
+StatusBubble* BrowserWindowGtk::GetStatusBubble() {
+ return status_bubble_.get();
+}
+
+void BrowserWindowGtk::SelectedTabToolbarSizeChanged(bool is_animating) {
+ // On Windows, this is used for a performance optimization.
+ // http://code.google.com/p/chromium/issues/detail?id=12291
+}
+
+void BrowserWindowGtk::SelectedTabExtensionShelfSizeChanged() {
+ NOTIMPLEMENTED();
+}
+
+void BrowserWindowGtk::UpdateTitleBar() {
+ string16 title = browser_->GetWindowTitleForCurrentTab();
+ gtk_window_set_title(window_, UTF16ToUTF8(title).c_str());
+ if (ShouldShowWindowIcon())
+ titlebar_->UpdateTitleAndIcon();
+}
+
+void BrowserWindowGtk::ShelfVisibilityChanged() {
+ MaybeShowBookmarkBar(browser_->GetSelectedTabContents(), false);
+}
+
+void BrowserWindowGtk::UpdateDevTools() {
+ UpdateDevToolsForContents(
+ browser_->tabstrip_model()->GetSelectedTabContents());
+}
+
+void BrowserWindowGtk::UpdateLoadingAnimations(bool should_animate) {
+ if (should_animate) {
+ if (!loading_animation_timer_.IsRunning()) {
+ // Loads are happening, and the timer isn't running, so start it.
+ loading_animation_timer_.Start(
+ base::TimeDelta::FromMilliseconds(kLoadingAnimationFrameTimeMs), this,
+ &BrowserWindowGtk::LoadingAnimationCallback);
+ }
+ } else {
+ if (loading_animation_timer_.IsRunning()) {
+ loading_animation_timer_.Stop();
+ // Loads are now complete, update the state if a task was scheduled.
+ LoadingAnimationCallback();
+ }
+ }
+}
+
+void BrowserWindowGtk::LoadingAnimationCallback() {
+ if (browser_->type() == Browser::TYPE_NORMAL) {
+ // Loading animations are shown in the tab for tabbed windows. We check the
+ // browser type instead of calling IsTabStripVisible() because the latter
+ // will return false for fullscreen windows, but we still need to update
+ // their animations (so that when they come out of fullscreen mode they'll
+ // be correct).
+ tabstrip_->UpdateLoadingAnimations();
+ } else if (ShouldShowWindowIcon()) {
+ // ... or in the window icon area for popups and app windows.
+ TabContents* tab_contents = browser_->GetSelectedTabContents();
+ // GetSelectedTabContents can return NULL for example under Purify when
+ // the animations are running slowly and this function is called on
+ // a timer through LoadingAnimationCallback.
+ titlebar_->UpdateThrobber(tab_contents);
+ }
+}
+
+void BrowserWindowGtk::SetStarredState(bool is_starred) {
+ toolbar_->GetLocationBarView()->SetStarred(is_starred);
+}
+
+gfx::Rect BrowserWindowGtk::GetRestoredBounds() const {
+ return restored_bounds_;
+}
+
+bool BrowserWindowGtk::IsMaximized() const {
+ return (state_ & GDK_WINDOW_STATE_MAXIMIZED);
+}
+
+bool BrowserWindowGtk::ShouldDrawContentDropShadow() {
+ return !IsMaximized() && UseCustomFrame();
+}
+
+void BrowserWindowGtk::SetFullscreen(bool fullscreen) {
+ // gtk_window_(un)fullscreen asks the window manager to toggle the EWMH
+ // for fullscreen windows. Not all window managers support this.
+ if (fullscreen) {
+ gtk_window_fullscreen(window_);
+ } else {
+ gtk_window_unfullscreen(window_);
+ }
+}
+
+bool BrowserWindowGtk::IsFullscreen() const {
+ return (state_ & GDK_WINDOW_STATE_FULLSCREEN);
+}
+
+bool BrowserWindowGtk::IsFullscreenBubbleVisible() const {
+ return fullscreen_exit_bubble_.get() ? true : false;
+}
+
+LocationBar* BrowserWindowGtk::GetLocationBar() const {
+ return toolbar_->GetLocationBar();
+}
+
+void BrowserWindowGtk::SetFocusToLocationBar(bool select_all) {
+ if (!IsFullscreen())
+ GetLocationBar()->FocusLocation(select_all);
+}
+
+void BrowserWindowGtk::UpdateReloadStopState(bool is_loading, bool force) {
+ toolbar_->GetReloadButton()->ChangeMode(
+ is_loading ? ReloadButtonGtk::MODE_STOP : ReloadButtonGtk::MODE_RELOAD,
+ force);
+}
+
+void BrowserWindowGtk::UpdateToolbar(TabContents* contents,
+ bool should_restore_state) {
+ toolbar_->UpdateTabContents(contents, should_restore_state);
+}
+
+void BrowserWindowGtk::FocusToolbar() {
+ NOTIMPLEMENTED();
+}
+
+void BrowserWindowGtk::FocusAppMenu() {
+ NOTIMPLEMENTED();
+}
+
+void BrowserWindowGtk::FocusBookmarksToolbar() {
+ NOTIMPLEMENTED();
+}
+
+void BrowserWindowGtk::FocusChromeOSStatus() {
+ NOTIMPLEMENTED();
+}
+
+void BrowserWindowGtk::RotatePaneFocus(bool forwards) {
+ NOTIMPLEMENTED();
+}
+
+bool BrowserWindowGtk::IsBookmarkBarVisible() const {
+ return browser_->SupportsWindowFeature(Browser::FEATURE_BOOKMARKBAR) &&
+ bookmark_bar_.get() &&
+ browser_->profile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar);
+}
+
+bool BrowserWindowGtk::IsBookmarkBarAnimating() const {
+ if (IsBookmarkBarSupported() && bookmark_bar_->IsAnimating())
+ return true;
+ return false;
+}
+
+bool BrowserWindowGtk::IsToolbarVisible() const {
+ return IsToolbarSupported();
+}
+
+gfx::Rect BrowserWindowGtk::GetRootWindowResizerRect() const {
+ return gfx::Rect();
+}
+
+void BrowserWindowGtk::ConfirmAddSearchProvider(const TemplateURL* template_url,
+ Profile* profile) {
+ new EditSearchEngineDialog(window_, template_url, NULL, profile);
+}
+
+void BrowserWindowGtk::ToggleBookmarkBar() {
+ bookmark_utils::ToggleWhenVisible(browser_->profile());
+}
+
+void BrowserWindowGtk::ToggleExtensionShelf() {
+ NOTIMPLEMENTED();
+}
+
+views::Window* BrowserWindowGtk::ShowAboutChromeDialog() {
+ ShowAboutDialogForProfile(window_, browser_->profile());
+ return NULL;
+}
+
+void BrowserWindowGtk::ShowUpdateChromeDialog() {
+ UpdateRecommendedDialog::Show(window_);
+}
+
+void BrowserWindowGtk::ShowTaskManager() {
+ TaskManagerGtk::Show();
+}
+
+void BrowserWindowGtk::ShowBookmarkBubble(const GURL& url,
+ bool already_bookmarked) {
+ toolbar_->GetLocationBarView()->ShowStarBubble(url, !already_bookmarked);
+}
+
+bool BrowserWindowGtk::IsDownloadShelfVisible() const {
+ return download_shelf_.get() && download_shelf_->IsShowing();
+}
+
+DownloadShelf* BrowserWindowGtk::GetDownloadShelf() {
+ if (!download_shelf_.get())
+ download_shelf_.reset(new DownloadShelfGtk(browser_.get(),
+ render_area_vbox_));
+ return download_shelf_.get();
+}
+
+void BrowserWindowGtk::ShowReportBugDialog() {
+ NOTIMPLEMENTED();
+}
+
+void BrowserWindowGtk::ShowClearBrowsingDataDialog() {
+ ClearBrowsingDataDialogGtk::Show(window_, browser_->profile());
+}
+
+void BrowserWindowGtk::ShowImportDialog() {
+ ImportDialogGtk::Show(window_, browser_->profile(), ALL);
+}
+
+void BrowserWindowGtk::ShowSearchEnginesDialog() {
+ KeywordEditorView::Show(browser_->profile());
+}
+
+void BrowserWindowGtk::ShowPasswordManager() {
+ NOTIMPLEMENTED();
+}
+
+void BrowserWindowGtk::ShowRepostFormWarningDialog(TabContents* tab_contents) {
+ new RepostFormWarningGtk(GetNativeHandle(), tab_contents);
+}
+
+void BrowserWindowGtk::ShowContentSettingsWindow(
+ ContentSettingsType content_type,
+ Profile* profile) {
+ ContentSettingsWindowGtk::Show(GetNativeHandle(), content_type, profile);
+}
+
+void BrowserWindowGtk::ShowCollectedCookiesDialog(TabContents* tab_contents) {
+ // Deletes itself on close.
+ new CollectedCookiesGtk(GetNativeHandle(), tab_contents);
+}
+
+void BrowserWindowGtk::ShowProfileErrorDialog(int message_id) {
+ std::string title = l10n_util::GetStringUTF8(IDS_PRODUCT_NAME);
+ std::string message = l10n_util::GetStringUTF8(message_id);
+ GtkWidget* dialog = gtk_message_dialog_new(window_,
+ static_cast<GtkDialogFlags>(0), GTK_MESSAGE_WARNING, GTK_BUTTONS_OK,
+ "%s", message.c_str());
+ gtk_util::ApplyMessageDialogQuirks(dialog);
+ gtk_window_set_title(GTK_WINDOW(dialog), title.c_str());
+ g_signal_connect(dialog, "response", G_CALLBACK(gtk_widget_destroy), NULL);
+ gtk_widget_show_all(dialog);
+}
+
+void BrowserWindowGtk::ShowThemeInstallBubble() {
+ ThemeInstallBubbleViewGtk::Show(window_);
+}
+
+void BrowserWindowGtk::ShowHTMLDialog(HtmlDialogUIDelegate* delegate,
+ gfx::NativeWindow parent_window) {
+ HtmlDialogGtk::ShowHtmlDialogGtk(browser_.get(), delegate, parent_window);
+}
+
+void BrowserWindowGtk::UserChangedTheme() {
+ SetBackgroundColor();
+ gdk_window_invalidate_rect(GTK_WIDGET(window_)->window,
+ &GTK_WIDGET(window_)->allocation, TRUE);
+ UpdateWindowShape(bounds_.width(), bounds_.height());
+}
+
+int BrowserWindowGtk::GetExtraRenderViewHeight() const {
+ int sum = infobar_container_->TotalHeightOfAnimatingBars();
+ if (IsBookmarkBarSupported() && bookmark_bar_->IsAnimating())
+ sum += bookmark_bar_->GetHeight();
+ if (download_shelf_.get() && download_shelf_->IsClosing())
+ sum += download_shelf_->GetHeight();
+ return sum;
+}
+
+void BrowserWindowGtk::TabContentsFocused(TabContents* tab_contents) {
+ NOTIMPLEMENTED();
+}
+
+void BrowserWindowGtk::ShowPageInfo(Profile* profile,
+ const GURL& url,
+ const NavigationEntry::SSLStatus& ssl,
+ bool show_history) {
+ browser::ShowPageInfo(window_, profile, url, ssl, show_history);
+}
+
+void BrowserWindowGtk::ShowAppMenu() {
+ toolbar_->ShowAppMenu();
+}
+
+bool BrowserWindowGtk::PreHandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) {
+ GdkEventKey* os_event = event.os_event;
+
+ if (!os_event || event.type != WebKit::WebInputEvent::RawKeyDown)
+ return false;
+
+ // We first find out the browser command associated to the |event|.
+ // Then if the command is a reserved one, and should be processed immediately
+ // according to the |event|, the command will be executed immediately.
+ // Otherwise we just set |*is_keyboard_shortcut| properly and return false.
+
+ // First check if it's a custom accelerator.
+ int id = GetCustomCommandId(os_event);
+
+ // Then check if it's a predefined accelerator bound to the window.
+ if (id == -1) {
+ // This piece of code is based on the fact that calling
+ // gtk_window_activate_key() method against |window_| may only trigger a
+ // browser command execution, by matching a global accelerator
+ // defined in above |kAcceleratorMap|.
+ //
+ // Here we need to retrieve the command id (if any) associated to the
+ // keyboard event. Instead of looking up the command id in above
+ // |kAcceleratorMap| table by ourselves, we block the command execution of
+ // the |browser_| object then send the keyboard event to the |window_| by
+ // calling gtk_window_activate_key() method, as if we are activating an
+ // accelerator key. Then we can retrieve the command id from the
+ // |browser_| object.
+ //
+ // Pros of this approach:
+ // 1. We don't need to care about keyboard layout problem, as
+ // gtk_window_activate_key() method handles it for us.
+ //
+ // Cons:
+ // 1. The logic is a little complicated.
+ // 2. We should be careful not to introduce any accelerators that trigger
+ // customized code instead of browser commands.
+ browser_->SetBlockCommandExecution(true);
+ gtk_window_activate_key(window_, os_event);
+ // We don't need to care about the WindowOpenDisposition value,
+ // because all commands executed in this path use the default value.
+ id = browser_->GetLastBlockedCommand(NULL);
+ browser_->SetBlockCommandExecution(false);
+ }
+
+ if (id == -1)
+ return false;
+
+ if (browser_->IsReservedCommand(id) &&
+ ShouldExecuteReservedCommandImmediately(event, id)) {
+ // Executing the command may cause |this| object to be destroyed.
+ return ExecuteBrowserCommand(id);
+ }
+
+ // The |event| is a keyboard shortcut.
+ DCHECK(is_keyboard_shortcut != NULL);
+ *is_keyboard_shortcut = true;
+
+ return false;
+}
+
+void BrowserWindowGtk::HandleKeyboardEvent(
+ const NativeWebKeyboardEvent& event) {
+ GdkEventKey* os_event = event.os_event;
+
+ if (!os_event || event.type != WebKit::WebInputEvent::RawKeyDown)
+ return;
+
+ // Handles a key event in following sequence:
+ // 1. Our special key accelerators, such as ctrl-tab, etc.
+ // 2. Gtk accelerators.
+ // This sequence matches the default key press handler of GtkWindow.
+ //
+ // It's not necessary to care about the keyboard layout, as
+ // gtk_window_activate_key() takes care of it automatically.
+ int id = GetCustomCommandId(os_event);
+ if (id != -1)
+ ExecuteBrowserCommand(id);
+ else
+ gtk_window_activate_key(window_, os_event);
+}
+
+void BrowserWindowGtk::ShowCreateShortcutsDialog(TabContents* tab_contents) {
+ CreateApplicationShortcutsDialogGtk::Show(window_, tab_contents);
+}
+
+void BrowserWindowGtk::Cut() {
+ DoCutCopyPaste(this, &RenderViewHost::Cut, "cut-clipboard");
+}
+
+void BrowserWindowGtk::Copy() {
+ DoCutCopyPaste(this, &RenderViewHost::Copy, "copy-clipboard");
+}
+
+void BrowserWindowGtk::Paste() {
+ DoCutCopyPaste(this, &RenderViewHost::Paste, "paste-clipboard");
+}
+
+void BrowserWindowGtk::ConfirmBrowserCloseWithPendingDownloads() {
+ new DownloadInProgressDialogGtk(browser());
+}
+
+void BrowserWindowGtk::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type.value) {
+ case NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED:
+ MaybeShowBookmarkBar(browser_->GetSelectedTabContents(), true);
+ break;
+
+ case NotificationType::PREF_CHANGED: {
+ std::wstring* pref_name = Details<std::wstring>(details).ptr();
+ if (*pref_name == prefs::kUseCustomChromeFrame) {
+ UpdateCustomFrame();
+ } else {
+ NOTREACHED() << "Got pref change notification we didn't register for!";
+ }
+ break;
+ }
+
+ default:
+ NOTREACHED() << "Got a notification we didn't register for!";
+ }
+}
+
+void BrowserWindowGtk::TabDetachedAt(TabContents* contents, int index) {
+ // We use index here rather than comparing |contents| because by this time
+ // the model has already removed |contents| from its list, so
+ // browser_->GetSelectedTabContents() will return NULL or something else.
+ if (index == browser_->tabstrip_model()->selected_index())
+ infobar_container_->ChangeTabContents(NULL);
+ contents_container_->DetachTabContents(contents);
+ UpdateDevToolsForContents(NULL);
+}
+
+void BrowserWindowGtk::TabSelectedAt(TabContents* old_contents,
+ TabContents* new_contents,
+ int index,
+ bool user_gesture) {
+ DCHECK(old_contents != new_contents);
+
+ if (old_contents && !old_contents->is_being_destroyed())
+ old_contents->view()->StoreFocus();
+
+ // Update various elements that are interested in knowing the current
+ // TabContents.
+ infobar_container_->ChangeTabContents(new_contents);
+ contents_container_->SetTabContents(new_contents);
+ UpdateDevToolsForContents(new_contents);
+
+ new_contents->DidBecomeSelected();
+ // TODO(estade): after we manage browser activation, add a check to make sure
+ // we are the active browser before calling RestoreFocus().
+ if (!browser_->tabstrip_model()->closing_all()) {
+ new_contents->view()->RestoreFocus();
+ if (new_contents->find_ui_active())
+ browser_->GetFindBarController()->find_bar()->SetFocusAndSelection();
+ }
+
+ // Update all the UI bits.
+ UpdateTitleBar();
+ UpdateToolbar(new_contents, true);
+ UpdateUIForContents(new_contents);
+}
+
+void BrowserWindowGtk::TabStripEmpty() {
+ UpdateUIForContents(NULL);
+}
+
+void BrowserWindowGtk::ActiveWindowChanged(GdkWindow* active_window) {
+ // Do nothing if we're in the process of closing the browser window.
+ if (!window_)
+ return;
+
+ bool is_active = (GTK_WIDGET(window_)->window == active_window);
+ bool changed = (is_active != is_active_);
+
+ if (is_active && changed) {
+ // If there's an app modal dialog (e.g., JS alert), try to redirect
+ // the user's attention to the window owning the dialog.
+ if (Singleton<AppModalDialogQueue>()->HasActiveDialog()) {
+ Singleton<AppModalDialogQueue>()->ActivateModalDialog();
+ return;
+ }
+ }
+
+ is_active_ = is_active;
+ if (changed) {
+ SetBackgroundColor();
+ gdk_window_invalidate_rect(GTK_WIDGET(window_)->window,
+ &GTK_WIDGET(window_)->allocation, TRUE);
+ // For some reason, the above two calls cause the window shape to be
+ // lost so reset it.
+ UpdateWindowShape(bounds_.width(), bounds_.height());
+ }
+}
+
+void BrowserWindowGtk::MaybeShowBookmarkBar(TabContents* contents,
+ bool animate) {
+ if (!IsBookmarkBarSupported())
+ return;
+
+ bool show_bar = false;
+
+ if (IsBookmarkBarSupported() && contents) {
+ bookmark_bar_->SetProfile(contents->profile());
+ bookmark_bar_->SetPageNavigator(contents);
+ show_bar = true;
+ }
+
+ if (show_bar && contents && !contents->ShouldShowBookmarkBar()) {
+ PrefService* prefs = contents->profile()->GetPrefs();
+ show_bar = prefs->GetBoolean(prefs::kShowBookmarkBar) && !IsFullscreen();
+ }
+
+ if (show_bar) {
+ bookmark_bar_->Show(animate);
+ } else if (IsFullscreen()) {
+ bookmark_bar_->EnterFullscreen();
+ } else {
+ bookmark_bar_->Hide(animate);
+ }
+}
+
+void BrowserWindowGtk::UpdateDevToolsForContents(TabContents* contents) {
+ TabContents* old_devtools = devtools_container_->GetTabContents();
+ TabContents* devtools_contents = contents ?
+ DevToolsWindow::GetDevToolsContents(contents) : NULL;
+ if (old_devtools == devtools_contents)
+ return;
+
+ if (old_devtools)
+ devtools_container_->DetachTabContents(old_devtools);
+
+ devtools_container_->SetTabContents(devtools_contents);
+ if (devtools_contents) {
+ // TabContentsViewGtk::WasShown is not called when tab contents is shown by
+ // anything other than user selecting a Tab.
+ // See TabContentsViewWin::OnWindowPosChanged for reference on how it should
+ // be implemented.
+ devtools_contents->ShowContents();
+ }
+
+ bool should_show = old_devtools == NULL && devtools_contents != NULL;
+ bool should_hide = old_devtools != NULL && devtools_contents == NULL;
+ if (should_show) {
+ gtk_widget_show(devtools_container_->widget());
+ } else if (should_hide) {
+ // Store split offset when hiding devtools window only.
+ gint divider_offset = gtk_paned_get_position(GTK_PANED(contents_split_));
+ g_browser_process->local_state()->SetInteger(
+ prefs::kDevToolsSplitLocation, divider_offset);
+ gtk_widget_hide(devtools_container_->widget());
+ }
+}
+
+void BrowserWindowGtk::UpdateUIForContents(TabContents* contents) {
+ MaybeShowBookmarkBar(contents, false);
+}
+
+void BrowserWindowGtk::DestroyBrowser() {
+ browser_.reset();
+}
+
+void BrowserWindowGtk::OnBoundsChanged(const gfx::Rect& bounds) {
+ GetLocationBar()->location_entry()->ClosePopup();
+
+ if (bounds_.size() != bounds.size())
+ OnSizeChanged(bounds.width(), bounds.height());
+
+ // We update |bounds_| but not |restored_bounds_| here. The latter needs
+ // to be updated conditionally when the window is non-maximized and non-
+ // fullscreen, but whether those state updates have been processed yet is
+ // window-manager specific. We update |restored_bounds_| in the debounced
+ // handler below, after the window state has been updated.
+ bounds_ = bounds;
+
+ // When a window is moved or resized, GTK will call MainWindowConfigured()
+ // above. The GdkEventConfigure* that it gets doesn't have quite the right
+ // coordinates though (they're relative to the drawable window area, rather
+ // than any window manager decorations, if enabled), so we need to call
+ // gtk_window_get_position() to get the right values. (Otherwise session
+ // restore, if enabled, will restore windows to incorrect positions.) That's
+ // a round trip to the X server though, so we set a debounce timer and only
+ // call it (in OnDebouncedBoundsChanged() below) after we haven't seen a
+ // reconfigure event in a short while.
+ // We don't use Reset() because the timer may not yet be running.
+ // (In that case Stop() is a no-op.)
+ window_configure_debounce_timer_.Stop();
+ window_configure_debounce_timer_.Start(base::TimeDelta::FromMilliseconds(
+ kDebounceTimeoutMilliseconds), this,
+ &BrowserWindowGtk::OnDebouncedBoundsChanged);
+}
+
+void BrowserWindowGtk::OnDebouncedBoundsChanged() {
+ gint x, y;
+ gtk_window_get_position(window_, &x, &y);
+ gfx::Point origin(x, y);
+ bounds_.set_origin(origin);
+ if (!IsFullscreen() && !IsMaximized())
+ restored_bounds_ = bounds_;
+ SaveWindowPosition();
+}
+
+void BrowserWindowGtk::OnStateChanged(GdkWindowState state,
+ GdkWindowState changed_mask) {
+ state_ = state;
+
+ if (changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
+ bool is_fullscreen = state & GDK_WINDOW_STATE_FULLSCREEN;
+ browser_->UpdateCommandsForFullscreenMode(is_fullscreen);
+ if (is_fullscreen) {
+ UpdateCustomFrame();
+ toolbar_->Hide();
+ tabstrip_->Hide();
+ if (IsBookmarkBarSupported())
+ bookmark_bar_->EnterFullscreen();
+ bool is_kiosk =
+ CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode);
+ if (!is_kiosk) {
+ fullscreen_exit_bubble_.reset(new FullscreenExitBubbleGtk(
+ GTK_FLOATING_CONTAINER(render_area_floating_container_)));
+ }
+ gtk_widget_hide(toolbar_border_);
+ } else {
+ fullscreen_exit_bubble_.reset();
+ UpdateCustomFrame();
+ ShowSupportedWindowFeatures();
+ }
+ }
+
+ UpdateWindowShape(bounds_.width(), bounds_.height());
+ SaveWindowPosition();
+}
+
+void BrowserWindowGtk::UnMaximize() {
+ gtk_window_unmaximize(window_);
+
+ // It can happen that you end up with a window whose restore size is the same
+ // as the size of the screen, so unmaximizing it merely remaximizes it due to
+ // the same WM feature that SetWindowSize() works around. We try to detect
+ // this and resize the window to work around the issue.
+ if (bounds_.size() == restored_bounds_.size())
+ gtk_window_resize(window_, bounds_.width(), bounds_.height() - 1);
+}
+
+bool BrowserWindowGtk::CanClose() const {
+ // You cannot close a frame for which there is an active originating drag
+ // session.
+ if (tabstrip_->IsDragSessionActive())
+ return false;
+
+ // Give beforeunload handlers the chance to cancel the close before we hide
+ // the window below.
+ if (!browser_->ShouldCloseWindow())
+ return false;
+
+ if (browser_->tabstrip_model()->HasNonPhantomTabs()) {
+ // Tab strip isn't empty. Hide the window (so it appears to have closed
+ // immediately) and close all the tabs, allowing the renderers to shut
+ // down. When the tab strip is empty we'll be called back again.
+ gtk_widget_hide(GTK_WIDGET(window_));
+ browser_->OnWindowClosing();
+ return false;
+ }
+
+ // Empty TabStripModel, it's now safe to allow the Window to be closed.
+ NotificationService::current()->Notify(
+ NotificationType::WINDOW_CLOSED,
+ Source<GtkWindow>(window_),
+ NotificationService::NoDetails());
+ return true;
+}
+
+bool BrowserWindowGtk::ShouldShowWindowIcon() const {
+ return browser_->SupportsWindowFeature(Browser::FEATURE_TITLEBAR);
+}
+
+void BrowserWindowGtk::AddFindBar(FindBarGtk* findbar) {
+ gtk_floating_container_add_floating(
+ GTK_FLOATING_CONTAINER(render_area_floating_container_),
+ findbar->widget());
+}
+
+void BrowserWindowGtk::ResetCustomFrameCursor() {
+ if (!frame_cursor_)
+ return;
+
+ gdk_cursor_unref(frame_cursor_);
+ frame_cursor_ = NULL;
+ gdk_window_set_cursor(GTK_WIDGET(window_)->window, NULL);
+}
+
+// static
+BrowserWindowGtk* BrowserWindowGtk::GetBrowserWindowForNativeWindow(
+ gfx::NativeWindow window) {
+ if (window) {
+ return static_cast<BrowserWindowGtk*>(
+ g_object_get_qdata(G_OBJECT(window), GetBrowserWindowQuarkKey()));
+ }
+
+ return NULL;
+}
+
+// static
+GtkWindow* BrowserWindowGtk::GetBrowserWindowForXID(XID xid) {
+ std::map<XID, GtkWindow*>::iterator iter =
+ BrowserWindowGtk::xid_map_.find(xid);
+ return (iter != BrowserWindowGtk::xid_map_.end()) ? iter->second : NULL;
+}
+
+// static
+void BrowserWindowGtk::RegisterUserPrefs(PrefService* prefs) {
+ bool custom_frame_default = false;
+ // Avoid checking the window manager if we're not connected to an X server (as
+ // is the case in Valgrind tests).
+ if (x11_util::XDisplayExists() &&
+ !prefs->HasPrefPath(prefs::kUseCustomChromeFrame)) {
+ custom_frame_default = GetCustomFramePrefDefault();
+ }
+ prefs->RegisterBooleanPref(
+ prefs::kUseCustomChromeFrame, custom_frame_default);
+}
+
+void BrowserWindowGtk::BookmarkBarIsFloating(bool is_floating) {
+ toolbar_->UpdateForBookmarkBarVisibility(is_floating);
+
+ // This can be NULL during initialization of the bookmark bar.
+ if (bookmark_bar_.get())
+ PlaceBookmarkBar(is_floating);
+}
+
+void BrowserWindowGtk::QueueToolbarRedraw() {
+ gtk_widget_queue_draw(toolbar_->widget());
+}
+
+void BrowserWindowGtk::SetGeometryHints() {
+ // If we call gtk_window_maximize followed by gtk_window_present, compiz gets
+ // confused and maximizes the window, but doesn't set the
+ // GDK_WINDOW_STATE_MAXIMIZED bit. So instead, we keep track of whether to
+ // maximize and call it after gtk_window_present.
+ maximize_after_show_ = browser_->GetSavedMaximizedState();
+
+ gfx::Rect bounds = browser_->GetSavedWindowBounds();
+ // We don't blindly call SetBounds here: that sets a forced position
+ // on the window and we intentionally *don't* do that for normal
+ // windows. Most programs do not restore their window position on
+ // Linux, instead letting the window manager choose a position.
+ //
+ // However, in cases like dropping a tab where the bounds are
+ // specifically set, we do want to position explicitly. We also
+ // force the position as part of session restore, as applications
+ // that restore other, similar state (for instance GIMP, audacity,
+ // pidgin, dia, and gkrellm) do tend to restore their positions.
+ //
+ // For popup windows, we assume that if x == y == 0, the opening page
+ // did not specify a position. Let the WM position the popup instead.
+ bool is_popup = browser_->type() & Browser::TYPE_POPUP;
+ bool popup_without_position = is_popup && bounds.x() == 0 && bounds.y() == 0;
+ if (browser_->bounds_overridden() && !popup_without_position) {
+ // For popups, bounds are set in terms of the client area rather than the
+ // entire window.
+ SetBoundsImpl(bounds, !is_popup);
+ } else {
+ // Ignore the position but obey the size.
+ SetWindowSize(window_, bounds.width(), bounds.height());
+ }
+}
+
+void BrowserWindowGtk::ConnectHandlersToSignals() {
+ g_signal_connect(window_, "delete-event",
+ G_CALLBACK(MainWindowDeleteEvent), this);
+ g_signal_connect(window_, "destroy",
+ G_CALLBACK(MainWindowDestroy), this);
+ g_signal_connect(window_, "configure-event",
+ G_CALLBACK(MainWindowConfigured), this);
+ g_signal_connect(window_, "window-state-event",
+ G_CALLBACK(MainWindowStateChanged), this);
+ g_signal_connect(window_, "map",
+ G_CALLBACK(MainWindowMapped), NULL);
+ g_signal_connect(window_, "unmap",
+ G_CALLBACK(MainWindowUnMapped), NULL);
+ g_signal_connect(window_, "key-press-event",
+ G_CALLBACK(OnKeyPressThunk), this);
+ g_signal_connect(window_, "motion-notify-event",
+ G_CALLBACK(OnMouseMoveEventThunk), this);
+ g_signal_connect(window_, "button-press-event",
+ G_CALLBACK(OnButtonPressEventThunk), this);
+ g_signal_connect(window_, "focus-in-event",
+ G_CALLBACK(OnFocusInThunk), this);
+ g_signal_connect(window_, "focus-out-event",
+ G_CALLBACK(OnFocusOutThunk), this);
+}
+
+void BrowserWindowGtk::InitWidgets() {
+ ConnectHandlersToSignals();
+ bounds_ = restored_bounds_ = GetInitialWindowBounds(window_);
+
+ // This vbox encompasses all of the widgets within the browser. This is
+ // everything except the custom frame border.
+ window_vbox_ = gtk_vbox_new(FALSE, 0);
+ gtk_widget_show(window_vbox_);
+
+ // The window container draws the custom browser frame.
+ window_container_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
+ gtk_widget_set_name(window_container_, "chrome-custom-frame-border");
+ gtk_widget_set_app_paintable(window_container_, TRUE);
+ gtk_widget_set_double_buffered(window_container_, FALSE);
+ gtk_widget_set_redraw_on_allocate(window_container_, TRUE);
+ g_signal_connect(window_container_, "expose-event",
+ G_CALLBACK(&OnCustomFrameExposeThunk), this);
+ gtk_container_add(GTK_CONTAINER(window_container_), window_vbox_);
+
+ tabstrip_.reset(new TabStripGtk(browser_->tabstrip_model(), this));
+ tabstrip_->Init();
+
+ // Build the titlebar (tabstrip + header space + min/max/close buttons).
+ titlebar_.reset(new BrowserTitlebar(this, window_));
+
+ // Insert the tabstrip into the window.
+ gtk_box_pack_start(GTK_BOX(window_vbox_), titlebar_->widget(), FALSE, FALSE,
+ 0);
+
+ toolbar_.reset(new BrowserToolbarGtk(browser_.get(), this));
+ toolbar_->Init(browser_->profile(), window_);
+ gtk_box_pack_start(GTK_BOX(window_vbox_), toolbar_->widget(),
+ FALSE, FALSE, 0);
+ // This vbox surrounds the render area: find bar, info bars and render view.
+ // The reason is that this area as a whole needs to be grouped in its own
+ // GdkWindow hierarchy so that animations originating inside it (infobar,
+ // download shelf, find bar) are all clipped to that area. This is why
+ // |render_area_vbox_| is packed in |render_area_event_box_|.
+ render_area_vbox_ = gtk_vbox_new(FALSE, 0);
+ gtk_widget_set_name(render_area_vbox_, "chrome-render-area-vbox");
+ render_area_floating_container_ = gtk_floating_container_new();
+ gtk_container_add(GTK_CONTAINER(render_area_floating_container_),
+ render_area_vbox_);
+
+ toolbar_border_ = gtk_event_box_new();
+ gtk_box_pack_start(GTK_BOX(render_area_vbox_),
+ toolbar_border_, FALSE, FALSE, 0);
+ gtk_widget_set_size_request(toolbar_border_, -1, 1);
+ gtk_widget_set_no_show_all(toolbar_border_, TRUE);
+
+ if (IsToolbarSupported())
+ gtk_widget_show(toolbar_border_);
+
+ infobar_container_.reset(new InfoBarContainerGtk(browser_->profile()));
+ gtk_box_pack_start(GTK_BOX(render_area_vbox_),
+ infobar_container_->widget(),
+ FALSE, FALSE, 0);
+
+ status_bubble_.reset(new StatusBubbleGtk(browser_->profile()));
+
+ contents_container_.reset(new TabContentsContainerGtk(status_bubble_.get()));
+ devtools_container_.reset(new TabContentsContainerGtk(NULL));
+ ViewIDUtil::SetID(devtools_container_->widget(), VIEW_ID_DEV_TOOLS_DOCKED);
+ contents_split_ = gtk_vpaned_new();
+ gtk_paned_pack1(GTK_PANED(contents_split_), contents_container_->widget(),
+ TRUE, TRUE);
+ gtk_paned_pack2(GTK_PANED(contents_split_), devtools_container_->widget(),
+ FALSE, TRUE);
+ gtk_box_pack_end(GTK_BOX(render_area_vbox_), contents_split_, TRUE, TRUE, 0);
+ // Restore split offset.
+ int split_offset = g_browser_process->local_state()->GetInteger(
+ prefs::kDevToolsSplitLocation);
+ if (split_offset != -1) {
+ gtk_paned_set_position(GTK_PANED(contents_split_), split_offset);
+ } else {
+ gtk_widget_set_size_request(devtools_container_->widget(), -1,
+ kDefaultDevToolsHeight);
+ }
+ gtk_widget_show_all(render_area_floating_container_);
+ gtk_widget_hide(devtools_container_->widget());
+ render_area_event_box_ = gtk_event_box_new();
+ // Set a white background so during startup the user sees white in the
+ // content area before we get a TabContents in place.
+ gtk_widget_modify_bg(render_area_event_box_, GTK_STATE_NORMAL,
+ &gfx::kGdkWhite);
+ gtk_container_add(GTK_CONTAINER(render_area_event_box_),
+ render_area_floating_container_);
+ gtk_widget_show(render_area_event_box_);
+ gtk_box_pack_end(GTK_BOX(window_vbox_), render_area_event_box_,
+ TRUE, TRUE, 0);
+
+ if (IsBookmarkBarSupported()) {
+ bookmark_bar_.reset(new BookmarkBarGtk(this,
+ browser_->profile(),
+ browser_.get(),
+ tabstrip_.get()));
+ PlaceBookmarkBar(false);
+ gtk_widget_show(bookmark_bar_->widget());
+ }
+
+ // We have to realize the window before we try to apply a window shape mask.
+ gtk_widget_realize(GTK_WIDGET(window_));
+ state_ = gdk_window_get_state(GTK_WIDGET(window_)->window);
+ // Note that calling this the first time is necessary to get the
+ // proper control layout.
+ UpdateCustomFrame();
+
+ gtk_container_add(GTK_CONTAINER(window_), window_container_);
+ gtk_widget_show(window_container_);
+ browser_->tabstrip_model()->AddObserver(this);
+}
+
+void BrowserWindowGtk::SetBackgroundColor() {
+ Profile* profile = browser()->profile();
+ GtkThemeProvider* theme_provider = GtkThemeProvider::GetFrom(profile);
+ int frame_color_id;
+ if (UsingCustomPopupFrame()) {
+ frame_color_id = BrowserThemeProvider::COLOR_TOOLBAR;
+ } else if (IsActive()) {
+ frame_color_id = browser()->profile()->IsOffTheRecord()
+ ? BrowserThemeProvider::COLOR_FRAME_INCOGNITO
+ : BrowserThemeProvider::COLOR_FRAME;
+ } else {
+ frame_color_id = browser()->profile()->IsOffTheRecord()
+ ? BrowserThemeProvider::COLOR_FRAME_INCOGNITO_INACTIVE
+ : BrowserThemeProvider::COLOR_FRAME_INACTIVE;
+ }
+
+ SkColor frame_color = theme_provider->GetColor(frame_color_id);
+
+ // Paint the frame color on the left, right and bottom.
+ GdkColor frame_color_gdk = SkColorToGdkColor(frame_color);
+ gtk_widget_modify_bg(GTK_WIDGET(window_), GTK_STATE_NORMAL,
+ &frame_color_gdk);
+
+ // Set the color of the dev tools divider.
+ gtk_widget_modify_bg(contents_split_, GTK_STATE_NORMAL, &frame_color_gdk);
+
+ // When the cursor is over the divider, GTK+ normally lightens the background
+ // color by 1.3 (see LIGHTNESS_MULT in gtkstyle.c). Since we're setting the
+ // color, override the prelight also.
+ color_utils::HSL hsl = { -1, 0.5, 0.65 };
+ SkColor frame_prelight_color = color_utils::HSLShift(frame_color, hsl);
+ GdkColor frame_prelight_color_gdk = SkColorToGdkColor(frame_prelight_color);
+ gtk_widget_modify_bg(contents_split_, GTK_STATE_PRELIGHT,
+ &frame_prelight_color_gdk);
+
+ GdkColor border_color = theme_provider->GetBorderColor();
+ gtk_widget_modify_bg(toolbar_border_, GTK_STATE_NORMAL, &border_color);
+}
+
+void BrowserWindowGtk::OnSizeChanged(int width, int height) {
+ UpdateWindowShape(width, height);
+}
+
+void BrowserWindowGtk::UpdateWindowShape(int width, int height) {
+ if (UseCustomFrame() && !IsFullscreen() && !IsMaximized()) {
+ // Make the corners rounded. We set a mask that includes most of the
+ // window except for a few pixels in each corner.
+ GdkRectangle top_top_rect = { 3, 0, width - 6, 1 };
+ GdkRectangle top_mid_rect = { 1, 1, width - 2, 2 };
+ GdkRectangle mid_rect = { 0, 3, width, height - 6 };
+ // The bottom two rects are mirror images of the top two rects.
+ GdkRectangle bot_mid_rect = top_mid_rect;
+ bot_mid_rect.y = height - 3;
+ GdkRectangle bot_bot_rect = top_top_rect;
+ bot_bot_rect.y = height - 1;
+ GdkRegion* mask = gdk_region_rectangle(&top_top_rect);
+ gdk_region_union_with_rect(mask, &top_mid_rect);
+ gdk_region_union_with_rect(mask, &mid_rect);
+ gdk_region_union_with_rect(mask, &bot_mid_rect);
+ gdk_region_union_with_rect(mask, &bot_bot_rect);
+ gdk_window_shape_combine_region(GTK_WIDGET(window_)->window, mask, 0, 0);
+ gdk_region_destroy(mask);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(window_container_), 1,
+ kFrameBorderThickness, kFrameBorderThickness, kFrameBorderThickness);
+ } else {
+ // XFCE disables the system decorations if there's an xshape set.
+ if (UseCustomFrame()) {
+ // Disable rounded corners. Simply passing in a NULL region doesn't
+ // seem to work on KWin, so manually set the shape to the whole window.
+ GdkRectangle rect = { 0, 0, width, height };
+ GdkRegion* mask = gdk_region_rectangle(&rect);
+ gdk_window_shape_combine_region(GTK_WIDGET(window_)->window, mask, 0, 0);
+ gdk_region_destroy(mask);
+ } else {
+ gdk_window_shape_combine_region(GTK_WIDGET(window_)->window, NULL, 0, 0);
+ }
+ gtk_alignment_set_padding(GTK_ALIGNMENT(window_container_), 0, 0, 0, 0);
+ }
+}
+
+void BrowserWindowGtk::ConnectAccelerators() {
+ accel_group_ = gtk_accel_group_new();
+ gtk_window_add_accel_group(window_, accel_group_);
+
+ AcceleratorsGtk* accelerators = Singleton<AcceleratorsGtk>().get();
+ for (AcceleratorsGtk::const_iterator iter = accelerators->begin();
+ iter != accelerators->end(); ++iter) {
+ gtk_accel_group_connect(
+ accel_group_,
+ iter->second.GetGdkKeyCode(),
+ static_cast<GdkModifierType>(iter->second.modifiers()),
+ GtkAccelFlags(0),
+ g_cclosure_new(G_CALLBACK(OnGtkAccelerator),
+ GINT_TO_POINTER(iter->first), NULL));
+ }
+}
+
+void BrowserWindowGtk::UpdateCustomFrame() {
+ gtk_window_set_decorated(window_, !UseCustomFrame());
+ titlebar_->UpdateCustomFrame(UseCustomFrame() && !IsFullscreen());
+ UpdateWindowShape(bounds_.width(), bounds_.height());
+}
+
+void BrowserWindowGtk::SaveWindowPosition() {
+ // Browser::SaveWindowPlacement is used for session restore.
+ if (browser_->ShouldSaveWindowPlacement())
+ browser_->SaveWindowPlacement(restored_bounds_, IsMaximized());
+
+ // We also need to save the placement for startup.
+ // This is a web of calls between views and delegates on Windows, but the
+ // crux of the logic follows. See also cocoa/browser_window_controller.mm.
+ if (!g_browser_process->local_state())
+ return;
+
+ std::wstring window_name = browser_->GetWindowPlacementKey();
+ DictionaryValue* window_preferences =
+ g_browser_process->local_state()->GetMutableDictionary(
+ window_name.c_str());
+ // Note that we store left/top for consistency with Windows, but that we
+ // *don't* obey them; we only use them for computing width/height. See
+ // comments in SetGeometryHints().
+ window_preferences->SetInteger(L"left", restored_bounds_.x());
+ window_preferences->SetInteger(L"top", restored_bounds_.y());
+ window_preferences->SetInteger(L"right", restored_bounds_.right());
+ window_preferences->SetInteger(L"bottom", restored_bounds_.bottom());
+ window_preferences->SetBoolean(L"maximized", IsMaximized());
+
+ scoped_ptr<WindowSizer::MonitorInfoProvider> monitor_info_provider(
+ WindowSizer::CreateDefaultMonitorInfoProvider());
+ gfx::Rect work_area(
+ monitor_info_provider->GetMonitorWorkAreaMatching(restored_bounds_));
+ window_preferences->SetInteger(L"work_area_left", work_area.x());
+ window_preferences->SetInteger(L"work_area_top", work_area.y());
+ window_preferences->SetInteger(L"work_area_right", work_area.right());
+ window_preferences->SetInteger(L"work_area_bottom", work_area.bottom());
+}
+
+// static
+gboolean BrowserWindowGtk::OnGtkAccelerator(GtkAccelGroup* accel_group,
+ GObject* acceleratable,
+ guint keyval,
+ GdkModifierType modifier,
+ void* user_data) {
+ int command_id = GPOINTER_TO_INT(user_data);
+ BrowserWindowGtk* browser_window =
+ GetBrowserWindowForNativeWindow(GTK_WINDOW(acceleratable));
+ DCHECK(browser_window != NULL);
+ return browser_window->ExecuteBrowserCommand(command_id);
+}
+
+// Let the focused widget have first crack at the key event so we don't
+// override their accelerators.
+gboolean BrowserWindowGtk::OnKeyPress(GtkWidget* widget, GdkEventKey* event) {
+ // If a widget besides the native view is focused, we have to try to handle
+ // the custom accelerators before letting it handle them.
+ TabContents* current_tab_contents =
+ browser()->tabstrip_model()->GetSelectedTabContents();
+ // The current tab might not have a render view if it crashed.
+ if (!current_tab_contents || !current_tab_contents->GetContentNativeView() ||
+ !gtk_widget_is_focus(current_tab_contents->GetContentNativeView())) {
+ int command_id = GetCustomCommandId(event);
+ if (command_id == -1)
+ command_id = GetPreHandleCommandId(event);
+
+ if (command_id != -1 && ExecuteBrowserCommand(command_id))
+ return TRUE;
+
+ // Propagate the key event to child widget first, so we don't override their
+ // accelerators.
+ if (!gtk_window_propagate_key_event(GTK_WINDOW(widget), event)) {
+ if (!gtk_window_activate_key(GTK_WINDOW(widget), event)) {
+ gtk_bindings_activate_event(GTK_OBJECT(widget), event);
+ }
+ }
+ } else {
+ bool rv = gtk_window_propagate_key_event(GTK_WINDOW(widget), event);
+ DCHECK(rv);
+ }
+
+ // Prevents the default handler from handling this event.
+ return TRUE;
+}
+
+gboolean BrowserWindowGtk::OnMouseMoveEvent(GtkWidget* widget,
+ GdkEventMotion* event) {
+ // This method is used to update the mouse cursor when over the edge of the
+ // custom frame. If the custom frame is off or we're over some other widget,
+ // do nothing.
+ if (!UseCustomFrame() || event->window != widget->window) {
+ // Reset the cursor.
+ if (frame_cursor_) {
+ gdk_cursor_unref(frame_cursor_);
+ frame_cursor_ = NULL;
+ gdk_window_set_cursor(GTK_WIDGET(window_)->window, NULL);
+ }
+ return FALSE;
+ }
+
+ // Update the cursor if we're on the custom frame border.
+ GdkWindowEdge edge;
+ bool has_hit_edge = GetWindowEdge(static_cast<int>(event->x),
+ static_cast<int>(event->y), &edge);
+ GdkCursorType new_cursor = GDK_LAST_CURSOR;
+ if (has_hit_edge)
+ new_cursor = GdkWindowEdgeToGdkCursorType(edge);
+
+ GdkCursorType last_cursor = GDK_LAST_CURSOR;
+ if (frame_cursor_)
+ last_cursor = frame_cursor_->type;
+
+ if (last_cursor != new_cursor) {
+ if (frame_cursor_) {
+ gdk_cursor_unref(frame_cursor_);
+ frame_cursor_ = NULL;
+ }
+ if (has_hit_edge) {
+ frame_cursor_ = gtk_util::GetCursor(new_cursor);
+ gdk_window_set_cursor(GTK_WIDGET(window_)->window,
+ frame_cursor_);
+ } else {
+ gdk_window_set_cursor(GTK_WIDGET(window_)->window, NULL);
+ }
+ }
+ return FALSE;
+}
+
+gboolean BrowserWindowGtk::OnButtonPressEvent(GtkWidget* widget,
+ GdkEventButton* event) {
+ // Handle back/forward.
+ if (event->type == GDK_BUTTON_PRESS) {
+ if (event->button == 8) {
+ browser_->GoBack(CURRENT_TAB);
+ return TRUE;
+ } else if (event->button == 9) {
+ browser_->GoForward(CURRENT_TAB);
+ return TRUE;
+ }
+ }
+
+ // Handle left, middle and right clicks. In particular, we care about clicks
+ // in the custom frame border and clicks in the titlebar.
+
+ // Make the button press coordinate relative to the browser window.
+ int win_x, win_y;
+ gdk_window_get_origin(GTK_WIDGET(window_)->window, &win_x, &win_y);
+
+ GdkWindowEdge edge;
+ gfx::Point point(static_cast<int>(event->x_root - win_x),
+ static_cast<int>(event->y_root - win_y));
+ bool has_hit_edge = GetWindowEdge(point.x(), point.y(), &edge);
+
+ // Ignore clicks that are in/below the browser toolbar.
+ GtkWidget* toolbar = toolbar_->widget();
+ if (!GTK_WIDGET_VISIBLE(toolbar)) {
+ // If the toolbar is not showing, use the location of web contents as the
+ // boundary of where to ignore clicks.
+ toolbar = render_area_vbox_;
+ }
+ gint toolbar_y;
+ gtk_widget_get_pointer(toolbar, NULL, &toolbar_y);
+ bool has_hit_titlebar = !IsFullscreen() && (toolbar_y < 0)
+ && !has_hit_edge;
+ if (event->button == 1) {
+ if (GDK_BUTTON_PRESS == event->type) {
+ guint32 last_click_time = last_click_time_;
+ gfx::Point last_click_position = last_click_position_;
+ last_click_time_ = event->time;
+ last_click_position_ = gfx::Point(static_cast<int>(event->x),
+ static_cast<int>(event->y));
+
+ // Raise the window after a click on either the titlebar or the border to
+ // match the behavior of most window managers, unless that behavior has
+ // been suppressed.
+ if ((has_hit_titlebar || has_hit_edge) && !suppress_window_raise_)
+ gdk_window_raise(GTK_WIDGET(window_)->window);
+
+ if (has_hit_titlebar) {
+ // We want to start a move when the user single clicks, but not start a
+ // move when the user double clicks. However, a double click sends the
+ // following GDK events: GDK_BUTTON_PRESS, GDK_BUTTON_RELEASE,
+ // GDK_BUTTON_PRESS, GDK_2BUTTON_PRESS, GDK_BUTTON_RELEASE. If we
+ // start a gtk_window_begin_move_drag on the second GDK_BUTTON_PRESS,
+ // the call to gtk_window_maximize fails. To work around this, we
+ // keep track of the last click and if it's going to be a double click,
+ // we don't call gtk_window_begin_move_drag.
+ static GtkSettings* settings = gtk_settings_get_default();
+ gint double_click_time = 250;
+ gint double_click_distance = 5;
+ g_object_get(G_OBJECT(settings),
+ "gtk-double-click-time", &double_click_time,
+ "gtk-double-click-distance", &double_click_distance,
+ NULL);
+
+ guint32 click_time = event->time - last_click_time;
+ int click_move_x = static_cast<int>(event->x - last_click_position.x());
+ int click_move_y = static_cast<int>(event->y - last_click_position.y());
+
+ if (click_time > static_cast<guint32>(double_click_time) ||
+ click_move_x > double_click_distance ||
+ click_move_y > double_click_distance) {
+ // Ignore drag requests if the window is the size of the screen.
+ // We do this to avoid triggering fullscreen mode in metacity
+ // (without the --no-force-fullscreen flag) and in compiz (with
+ // Legacy Fullscreen Mode enabled).
+ GdkScreen* screen = gtk_window_get_screen(window_);
+ if (bounds_.width() != gdk_screen_get_width(screen) ||
+ bounds_.height() != gdk_screen_get_height(screen)) {
+ gtk_window_begin_move_drag(window_, event->button,
+ static_cast<gint>(event->x_root),
+ static_cast<gint>(event->y_root),
+ event->time);
+ }
+ return TRUE;
+ }
+ } else if (has_hit_edge) {
+ gtk_window_begin_resize_drag(window_, edge, event->button,
+ static_cast<gint>(event->x_root),
+ static_cast<gint>(event->y_root),
+ event->time);
+ return TRUE;
+ }
+ } else if (GDK_2BUTTON_PRESS == event->type) {
+ if (has_hit_titlebar) {
+ // Maximize/restore on double click.
+ if (IsMaximized()) {
+ UnMaximize();
+ } else {
+ gtk_window_maximize(window_);
+ }
+ return TRUE;
+ }
+ }
+ } else if (event->button == 2) {
+ if (has_hit_titlebar || has_hit_edge) {
+ gdk_window_lower(GTK_WIDGET(window_)->window);
+ }
+ return TRUE;
+ } else if (event->button == 3) {
+ if (has_hit_titlebar) {
+ titlebar_->ShowContextMenu();
+ return TRUE;
+ }
+ }
+
+ return FALSE; // Continue to propagate the event.
+}
+
+// static
+void BrowserWindowGtk::MainWindowMapped(GtkWidget* widget) {
+ // Map the X Window ID of the window to our window.
+ XID xid = x11_util::GetX11WindowFromGtkWidget(widget);
+ BrowserWindowGtk::xid_map_.insert(
+ std::pair<XID, GtkWindow*>(xid, GTK_WINDOW(widget)));
+}
+
+// static
+void BrowserWindowGtk::MainWindowUnMapped(GtkWidget* widget) {
+ // Unmap the X Window ID.
+ XID xid = x11_util::GetX11WindowFromGtkWidget(widget);
+ BrowserWindowGtk::xid_map_.erase(xid);
+}
+
+gboolean BrowserWindowGtk::OnFocusIn(GtkWidget* widget,
+ GdkEventFocus* event) {
+ BrowserList::SetLastActive(browser_.get());
+ return FALSE;
+}
+
+gboolean BrowserWindowGtk::OnFocusOut(GtkWidget* widget,
+ GdkEventFocus* event) {
+ return FALSE;
+}
+
+bool BrowserWindowGtk::ExecuteBrowserCommand(int id) {
+ if (browser_->command_updater()->IsCommandEnabled(id)) {
+ browser_->ExecuteCommand(id);
+ return true;
+ }
+ return false;
+}
+
+void BrowserWindowGtk::ShowSupportedWindowFeatures() {
+ if (IsTabStripSupported())
+ tabstrip_->Show();
+
+ if (IsToolbarSupported()) {
+ toolbar_->Show();
+ gtk_widget_show(toolbar_border_);
+ gdk_window_lower(toolbar_border_->window);
+ }
+
+ if (IsBookmarkBarSupported())
+ MaybeShowBookmarkBar(browser_->GetSelectedTabContents(), false);
+}
+
+void BrowserWindowGtk::HideUnsupportedWindowFeatures() {
+ if (!IsTabStripSupported())
+ tabstrip_->Hide();
+
+ if (!IsToolbarSupported())
+ toolbar_->Hide();
+
+ // If the bookmark bar shelf is unsupported, then we never create it.
+}
+
+bool BrowserWindowGtk::IsTabStripSupported() const {
+ return browser_->SupportsWindowFeature(Browser::FEATURE_TABSTRIP);
+}
+
+bool BrowserWindowGtk::IsToolbarSupported() const {
+ return browser_->SupportsWindowFeature(Browser::FEATURE_TOOLBAR) ||
+ browser_->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR);
+}
+
+bool BrowserWindowGtk::IsBookmarkBarSupported() const {
+ return browser_->SupportsWindowFeature(Browser::FEATURE_BOOKMARKBAR);
+}
+
+bool BrowserWindowGtk::UsingCustomPopupFrame() const {
+ GtkThemeProvider* theme_provider = GtkThemeProvider::GetFrom(
+ browser()->profile());
+ return !theme_provider->UseGtkTheme() &&
+ browser()->type() & Browser::TYPE_POPUP;
+}
+
+bool BrowserWindowGtk::GetWindowEdge(int x, int y, GdkWindowEdge* edge) {
+ if (!UseCustomFrame())
+ return false;
+
+ if (IsMaximized() || IsFullscreen())
+ return false;
+
+ if (x < kFrameBorderThickness) {
+ // Left edge.
+ if (y < kResizeAreaCornerSize - kTopResizeAdjust) {
+ *edge = GDK_WINDOW_EDGE_NORTH_WEST;
+ } else if (y < bounds_.height() - kResizeAreaCornerSize) {
+ *edge = GDK_WINDOW_EDGE_WEST;
+ } else {
+ *edge = GDK_WINDOW_EDGE_SOUTH_WEST;
+ }
+ return true;
+ } else if (x < bounds_.width() - kFrameBorderThickness) {
+ if (y < kFrameBorderThickness - kTopResizeAdjust) {
+ // Top edge.
+ if (x < kResizeAreaCornerSize) {
+ *edge = GDK_WINDOW_EDGE_NORTH_WEST;
+ } else if (x < bounds_.width() - kResizeAreaCornerSize) {
+ *edge = GDK_WINDOW_EDGE_NORTH;
+ } else {
+ *edge = GDK_WINDOW_EDGE_NORTH_EAST;
+ }
+ } else if (y < bounds_.height() - kFrameBorderThickness) {
+ // Ignore the middle content area.
+ return false;
+ } else {
+ // Bottom edge.
+ if (x < kResizeAreaCornerSize) {
+ *edge = GDK_WINDOW_EDGE_SOUTH_WEST;
+ } else if (x < bounds_.width() - kResizeAreaCornerSize) {
+ *edge = GDK_WINDOW_EDGE_SOUTH;
+ } else {
+ *edge = GDK_WINDOW_EDGE_SOUTH_EAST;
+ }
+ }
+ return true;
+ } else {
+ // Right edge.
+ if (y < kResizeAreaCornerSize - kTopResizeAdjust) {
+ *edge = GDK_WINDOW_EDGE_NORTH_EAST;
+ } else if (y < bounds_.height() - kResizeAreaCornerSize) {
+ *edge = GDK_WINDOW_EDGE_EAST;
+ } else {
+ *edge = GDK_WINDOW_EDGE_SOUTH_EAST;
+ }
+ return true;
+ }
+ NOTREACHED();
+}
+
+bool BrowserWindowGtk::UseCustomFrame() {
+ // We don't use the custom frame for app mode windows or app window popups.
+ return use_custom_frame_pref_.GetValue() &&
+ browser_->type() != Browser::TYPE_APP &&
+ browser_->type() != Browser::TYPE_APP_POPUP;
+}
+
+void BrowserWindowGtk::PlaceBookmarkBar(bool is_floating) {
+ GtkWidget* parent = gtk_widget_get_parent(bookmark_bar_->widget());
+ if (parent)
+ gtk_container_remove(GTK_CONTAINER(parent), bookmark_bar_->widget());
+
+ if (!is_floating) {
+ // Place the bookmark bar at the end of |window_vbox_|; this happens after
+ // we have placed the render area at the end of |window_vbox_| so we will
+ // be above the render area.
+ gtk_box_pack_end(GTK_BOX(window_vbox_), bookmark_bar_->widget(),
+ FALSE, FALSE, 0);
+ } else {
+ // Place the bookmark bar at the end of the render area; this happens after
+ // the tab contents container has been placed there so we will be
+ // above the webpage (in terms of y).
+ gtk_box_pack_end(GTK_BOX(render_area_vbox_), bookmark_bar_->widget(),
+ FALSE, FALSE, 0);
+ }
+}
+
+// static
+bool BrowserWindowGtk::GetCustomFramePrefDefault() {
+ std::string wm_name;
+ if (!x11_util::GetWindowManagerName(&wm_name))
+ return false;
+
+ // Ideally, we'd use the custom frame by default and just fall back on using
+ // system decorations for the few (?) tiling window managers where the custom
+ // frame doesn't make sense (e.g. awesome, ion3, ratpoison, xmonad, etc.) or
+ // other WMs where it has issues (e.g. Fluxbox -- see issue 19130). The EWMH
+ // _NET_SUPPORTING_WM property makes it easy to look up a name for the current
+ // WM, but at least some of the WMs in the latter group don't set it.
+ // Instead, we default to using system decorations for all WMs and
+ // special-case the ones where the custom frame should be used. These names
+ // are taken from the WMs' source code.
+ return (wm_name == "Blackbox" ||
+ wm_name == "compiz" ||
+ wm_name == "e16" || // Enlightenment DR16
+ wm_name == "Metacity" ||
+ wm_name == "Mutter" ||
+ wm_name == "Openbox" ||
+ wm_name == "Xfwm4");
+}