df09ff7eb6
Review URL: http://codereview.chromium.org/149367 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@20210 0039d316-1c4b-4281-b951-d872f2087c98
1405 linhas
48 KiB
C++
1405 linhas
48 KiB
C++
// Copyright (c) 2009 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 <X11/XF86keysym.h>
|
|
|
|
#include "app/resource_bundle.h"
|
|
#include "app/theme_provider.h"
|
|
#include "base/base_paths_linux.h"
|
|
#include "base/command_line.h"
|
|
#include "base/gfx/gtk_util.h"
|
|
#include "base/logging.h"
|
|
#include "base/message_loop.h"
|
|
#include "base/path_service.h"
|
|
#include "base/string_util.h"
|
|
#include "base/time.h"
|
|
#include "chrome/app/chrome_dll_resource.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/download/download_item_model.h"
|
|
#include "chrome/browser/download/download_manager.h"
|
|
#include "chrome/browser/gtk/about_chrome_dialog.h"
|
|
#include "chrome/browser/gtk/bookmark_bar_gtk.h"
|
|
#include "chrome/browser/gtk/bookmark_manager_gtk.h"
|
|
#include "chrome/browser/gtk/browser_titlebar.h"
|
|
#include "chrome/browser/gtk/browser_toolbar_gtk.h"
|
|
#include "chrome/browser/gtk/clear_browsing_data_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/go_button_gtk.h"
|
|
#include "chrome/browser/gtk/gtk_theme_provider.h"
|
|
#include "chrome/browser/gtk/import_dialog_gtk.h"
|
|
#include "chrome/browser/gtk/infobar_container_gtk.h"
|
|
#include "chrome/browser/gtk/keyword_editor_view.h"
|
|
#include "chrome/browser/gtk/nine_box.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/toolbar_star_toggle_gtk.h"
|
|
#include "chrome/browser/location_bar.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/common/notification_service.h"
|
|
#include "chrome/common/pref_names.h"
|
|
#include "chrome/common/pref_service.h"
|
|
#include "grit/app_resources.h"
|
|
#include "grit/theme_resources.h"
|
|
|
|
#if defined(OS_CHROMEOS)
|
|
#include "chrome/browser/views/panel_controller.h"
|
|
#include "chrome/browser/views/tabs/tab_overview_types.h"
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
// The number of milliseconds between loading animation frames.
|
|
const int kLoadingAnimationFrameTimeMs = 30;
|
|
|
|
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;
|
|
|
|
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);
|
|
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);
|
|
}
|
|
|
|
const struct AcceleratorMapping {
|
|
guint keyval;
|
|
int command_id;
|
|
GdkModifierType modifier_type;
|
|
} kAcceleratorMap[] = {
|
|
// Focus.
|
|
{ GDK_k, IDC_FOCUS_SEARCH, GDK_CONTROL_MASK },
|
|
{ XF86XK_Search, IDC_FOCUS_SEARCH, GdkModifierType(0) },
|
|
{ GDK_l, IDC_FOCUS_LOCATION, GDK_CONTROL_MASK },
|
|
{ GDK_d, IDC_FOCUS_LOCATION, GDK_MOD1_MASK },
|
|
{ GDK_F6, IDC_FOCUS_LOCATION, GdkModifierType(0) },
|
|
{ XF86XK_OpenURL, IDC_FOCUS_LOCATION, GdkModifierType(0) },
|
|
{ XF86XK_Go, IDC_FOCUS_LOCATION, GdkModifierType(0) },
|
|
|
|
// Tab/window controls.
|
|
{ GDK_Page_Down, IDC_SELECT_NEXT_TAB, GDK_CONTROL_MASK },
|
|
{ GDK_Page_Up, IDC_SELECT_PREVIOUS_TAB, GDK_CONTROL_MASK },
|
|
{ GDK_w, IDC_CLOSE_TAB, GDK_CONTROL_MASK },
|
|
{ GDK_t, IDC_RESTORE_TAB,
|
|
GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) },
|
|
|
|
{ GDK_1, IDC_SELECT_TAB_0, GDK_CONTROL_MASK },
|
|
{ GDK_2, IDC_SELECT_TAB_1, GDK_CONTROL_MASK },
|
|
{ GDK_3, IDC_SELECT_TAB_2, GDK_CONTROL_MASK },
|
|
{ GDK_4, IDC_SELECT_TAB_3, GDK_CONTROL_MASK },
|
|
{ GDK_5, IDC_SELECT_TAB_4, GDK_CONTROL_MASK },
|
|
{ GDK_6, IDC_SELECT_TAB_5, GDK_CONTROL_MASK },
|
|
{ GDK_7, IDC_SELECT_TAB_6, GDK_CONTROL_MASK },
|
|
{ GDK_8, IDC_SELECT_TAB_7, GDK_CONTROL_MASK },
|
|
{ GDK_9, IDC_SELECT_LAST_TAB, GDK_CONTROL_MASK },
|
|
|
|
{ GDK_1, IDC_SELECT_TAB_0, GDK_MOD1_MASK },
|
|
{ GDK_2, IDC_SELECT_TAB_1, GDK_MOD1_MASK },
|
|
{ GDK_3, IDC_SELECT_TAB_2, GDK_MOD1_MASK },
|
|
{ GDK_4, IDC_SELECT_TAB_3, GDK_MOD1_MASK },
|
|
{ GDK_5, IDC_SELECT_TAB_4, GDK_MOD1_MASK },
|
|
{ GDK_6, IDC_SELECT_TAB_5, GDK_MOD1_MASK },
|
|
{ GDK_7, IDC_SELECT_TAB_6, GDK_MOD1_MASK },
|
|
{ GDK_8, IDC_SELECT_TAB_7, GDK_MOD1_MASK },
|
|
{ GDK_9, IDC_SELECT_LAST_TAB, GDK_MOD1_MASK },
|
|
|
|
{ GDK_F4, IDC_CLOSE_TAB, GDK_CONTROL_MASK },
|
|
{ GDK_F4, IDC_CLOSE_WINDOW, GDK_MOD1_MASK },
|
|
|
|
// Zoom level.
|
|
{ GDK_plus, IDC_ZOOM_PLUS,
|
|
GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) },
|
|
{ GDK_equal, IDC_ZOOM_PLUS, GDK_CONTROL_MASK },
|
|
{ XF86XK_ZoomIn, IDC_ZOOM_PLUS, GdkModifierType(0) },
|
|
{ GDK_0, IDC_ZOOM_NORMAL, GDK_CONTROL_MASK },
|
|
{ GDK_minus, IDC_ZOOM_MINUS, GDK_CONTROL_MASK },
|
|
{ GDK_underscore, IDC_ZOOM_MINUS,
|
|
GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) },
|
|
{ XF86XK_ZoomOut, IDC_ZOOM_MINUS, GdkModifierType(0) },
|
|
|
|
// Find in page.
|
|
{ GDK_g, IDC_FIND_NEXT, GDK_CONTROL_MASK },
|
|
{ GDK_F3, IDC_FIND_NEXT, GdkModifierType(0) },
|
|
{ GDK_g, IDC_FIND_PREVIOUS,
|
|
GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) },
|
|
{ GDK_F3, IDC_FIND_PREVIOUS, GDK_SHIFT_MASK },
|
|
|
|
// Navigation / toolbar buttons.
|
|
{ GDK_Home, IDC_HOME, GDK_MOD1_MASK },
|
|
{ XF86XK_HomePage, IDC_HOME, GdkModifierType(0) },
|
|
{ GDK_Escape, IDC_STOP, GdkModifierType(0) },
|
|
{ XF86XK_Stop, IDC_STOP, GdkModifierType(0) },
|
|
{ GDK_Left, IDC_BACK, GDK_MOD1_MASK },
|
|
{ GDK_BackSpace, IDC_BACK, GdkModifierType(0) },
|
|
{ XF86XK_Back, IDC_BACK, GdkModifierType(0) },
|
|
{ GDK_Right, IDC_FORWARD, GDK_MOD1_MASK },
|
|
{ GDK_BackSpace, IDC_FORWARD, GDK_SHIFT_MASK },
|
|
{ XF86XK_Forward, IDC_FORWARD, GdkModifierType(0) },
|
|
{ GDK_r, IDC_RELOAD, GDK_CONTROL_MASK },
|
|
{ GDK_F5, IDC_RELOAD, GdkModifierType(0) },
|
|
{ GDK_F5, IDC_RELOAD, GDK_CONTROL_MASK },
|
|
{ XF86XK_Reload, IDC_RELOAD, GdkModifierType(0) },
|
|
{ XF86XK_Refresh, IDC_RELOAD, GdkModifierType(0) },
|
|
|
|
// Miscellany.
|
|
{ GDK_d, IDC_STAR, GDK_CONTROL_MASK },
|
|
{ XF86XK_AddFavorite, IDC_STAR, GdkModifierType(0) },
|
|
{ XF86XK_Favorites, IDC_SHOW_BOOKMARK_BAR, GdkModifierType(0) },
|
|
{ XF86XK_History, IDC_SHOW_HISTORY, GdkModifierType(0) },
|
|
{ GDK_o, IDC_OPEN_FILE, GDK_CONTROL_MASK },
|
|
{ GDK_F11, IDC_FULLSCREEN, GdkModifierType(0) },
|
|
{ GDK_u, IDC_VIEW_SOURCE, GDK_CONTROL_MASK },
|
|
{ GDK_p, IDC_PRINT, GDK_CONTROL_MASK },
|
|
{ GDK_Escape, IDC_TASK_MANAGER, GDK_SHIFT_MASK },
|
|
|
|
#if defined(OS_CHROMEOS)
|
|
{ GDK_f, IDC_FULLSCREEN,
|
|
GdkModifierType(GDK_CONTROL_MASK | GDK_MOD1_MASK) },
|
|
{ GDK_Delete, IDC_TASK_MANAGER,
|
|
GdkModifierType(GDK_CONTROL_MASK | GDK_MOD1_MASK) },
|
|
{ GDK_comma, IDC_CONTROL_PANEL, GdkModifierType(GDK_CONTROL_MASK) },
|
|
#endif
|
|
};
|
|
|
|
int GetCommandId(guint accel_key, GdkModifierType modifier) {
|
|
// Bug 9806: If capslock is on, we will get a capital letter as accel_key.
|
|
accel_key = gdk_keyval_to_lower(accel_key);
|
|
// Filter modifier to only include accelerator modifiers.
|
|
modifier = static_cast<GdkModifierType>(
|
|
modifier & gtk_accelerator_get_default_mod_mask());
|
|
for (size_t i = 0; i < arraysize(kAcceleratorMap); ++i) {
|
|
if (kAcceleratorMap[i].keyval == accel_key &&
|
|
kAcceleratorMap[i].modifier_type == modifier)
|
|
return kAcceleratorMap[i].command_id;
|
|
}
|
|
NOTREACHED();
|
|
return 0;
|
|
}
|
|
|
|
// An event handler for key press events. We need to special case key
|
|
// combinations that are not valid gtk accelerators. This function returns
|
|
// TRUE if it can handle the key press.
|
|
gboolean HandleCustomAccelerator(guint keyval, GdkModifierType modifier,
|
|
Browser* browser) {
|
|
// Filter modifier to only include accelerator modifiers.
|
|
modifier = static_cast<GdkModifierType>(
|
|
modifier & gtk_accelerator_get_default_mod_mask());
|
|
switch (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.
|
|
case GDK_Tab:
|
|
if (GDK_CONTROL_MASK == modifier) {
|
|
browser->ExecuteCommand(IDC_SELECT_NEXT_TAB);
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case GDK_ISO_Left_Tab:
|
|
if ((GDK_CONTROL_MASK | GDK_SHIFT_MASK) == modifier) {
|
|
browser->ExecuteCommand(IDC_SELECT_PREVIOUS_TAB);
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// Handle accelerators that we don't want the native widget to be able to
|
|
// override.
|
|
gboolean PreHandleAccelerator(guint keyval, GdkModifierType modifier,
|
|
Browser* browser) {
|
|
// Filter modifier to only include accelerator modifiers.
|
|
modifier = static_cast<GdkModifierType>(
|
|
modifier & gtk_accelerator_get_default_mod_mask());
|
|
switch (keyval) {
|
|
case GDK_Page_Down:
|
|
if (GDK_CONTROL_MASK == modifier) {
|
|
browser->ExecuteCommand(IDC_SELECT_NEXT_TAB);
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
case GDK_Page_Up:
|
|
if (GDK_CONTROL_MASK == modifier) {
|
|
browser->ExecuteCommand(IDC_SELECT_PREVIOUS_TAB);
|
|
return TRUE;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// Let the focused widget have first crack at the key event so we don't
|
|
// override their accelerators.
|
|
gboolean OnKeyPress(GtkWindow* window, GdkEventKey* event, Browser* browser) {
|
|
// 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())) {
|
|
if (HandleCustomAccelerator(event->keyval,
|
|
GdkModifierType(event->state), browser) ||
|
|
PreHandleAccelerator(event->keyval,
|
|
GdkModifierType(event->state), browser)) {
|
|
return TRUE;
|
|
}
|
|
|
|
return gtk_window_propagate_key_event(window, event);
|
|
} else {
|
|
bool rv = gtk_window_propagate_key_event(window, event);
|
|
DCHECK(rv);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
gboolean OnFocusIn(GtkWidget* widget, GdkEventFocus* event, Browser* browser) {
|
|
BrowserList::SetLastActive(browser);
|
|
return FALSE;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::map<XID, GtkWindow*> BrowserWindowGtk::xid_map_;
|
|
|
|
BrowserWindowGtk::BrowserWindowGtk(Browser* browser)
|
|
: browser_(browser),
|
|
full_screen_(false),
|
|
#if defined(OS_CHROMEOS)
|
|
drag_active_(false),
|
|
panel_controller_(NULL),
|
|
#endif
|
|
frame_cursor_(NULL) {
|
|
use_custom_frame_.Init(prefs::kUseCustomChromeFrame,
|
|
browser_->profile()->GetPrefs(), this);
|
|
window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
|
|
g_object_set_data(G_OBJECT(window_), kBrowserWindowKey, this);
|
|
gtk_widget_add_events(GTK_WIDGET(window_), GDK_BUTTON_PRESS_MASK |
|
|
GDK_POINTER_MOTION_MASK);
|
|
|
|
SetWindowIcon();
|
|
SetBackgroundColor();
|
|
SetGeometryHints();
|
|
ConnectHandlersToSignals();
|
|
ConnectAccelerators();
|
|
bounds_ = GetInitialWindowBounds(window_);
|
|
|
|
InitWidgets();
|
|
HideUnsupportedWindowFeatures();
|
|
|
|
registrar_.Add(this, NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED,
|
|
NotificationService::AllSources());
|
|
}
|
|
|
|
BrowserWindowGtk::~BrowserWindowGtk() {
|
|
browser_->tabstrip_model()->RemoveObserver(this);
|
|
|
|
if (frame_cursor_) {
|
|
gdk_cursor_unref(frame_cursor_);
|
|
frame_cursor_ = NULL;
|
|
}
|
|
}
|
|
|
|
void BrowserWindowGtk::HandleAccelerator(guint keyval,
|
|
GdkModifierType modifier) {
|
|
if (!HandleCustomAccelerator(keyval, modifier, browser_.get())) {
|
|
// Pass the accelerator on to GTK.
|
|
gtk_accel_groups_activate(G_OBJECT(window_), keyval, modifier);
|
|
}
|
|
}
|
|
|
|
gboolean BrowserWindowGtk::OnCustomFrameExpose(GtkWidget* widget,
|
|
GdkEventExpose* event,
|
|
BrowserWindowGtk* window) {
|
|
// TODO(tc): This will have to be dynamic once themes are supported. Maybe
|
|
// detect the theme install and delete the pointer?
|
|
static NineBox* custom_frame_border = NULL;
|
|
static NineBox* default_background = NULL;
|
|
static NineBox* default_background_otr = NULL;
|
|
|
|
ThemeProvider* theme_provider =
|
|
window->browser()->profile()->GetThemeProvider();
|
|
if (!default_background) {
|
|
default_background = new NineBox(theme_provider,
|
|
0, IDR_THEME_FRAME, 0, 0, 0, 0, 0, 0, 0);
|
|
default_background_otr = new NineBox(theme_provider,
|
|
0, IDR_THEME_FRAME_INCOGNITO, 0, 0, 0, 0, 0, 0, 0);
|
|
}
|
|
|
|
// Draw the default background.
|
|
cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(widget->window));
|
|
cairo_rectangle(cr, event->area.x, event->area.y, event->area.width,
|
|
event->area.height);
|
|
cairo_clip(cr);
|
|
NineBox* image = window->browser()->profile()->IsOffTheRecord()
|
|
? default_background_otr : default_background;
|
|
image->RenderTopCenterStrip(cr, 0, 0, widget->allocation.width);
|
|
cairo_destroy(cr);
|
|
|
|
// TODO(tc): Draw the theme overlay. The windows code is below.
|
|
// if (theme_provider->HasCustomImage(IDR_THEME_FRAME_OVERLAY)) {
|
|
// SkBitmap* theme_overlay = theme_provider->GetBitmapNamed(
|
|
// IDR_THEME_FRAME_OVERLAY);
|
|
// canvas->DrawBitmapInt(*theme_overlay, 0, 0);
|
|
// }
|
|
|
|
if (window->use_custom_frame_.GetValue() && !window->IsMaximized()) {
|
|
if (!custom_frame_border) {
|
|
custom_frame_border = new NineBox(
|
|
theme_provider,
|
|
IDR_WINDOW_TOP_LEFT_CORNER,
|
|
IDR_WINDOW_TOP_CENTER,
|
|
IDR_WINDOW_TOP_RIGHT_CORNER,
|
|
IDR_WINDOW_LEFT_SIDE,
|
|
NULL,
|
|
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::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());
|
|
|
|
#if defined(OS_CHROMEOS)
|
|
if (browser_->type() == Browser::TYPE_POPUP) {
|
|
panel_controller_ = new PanelController(this);
|
|
} else {
|
|
TabOverviewTypes::instance()->SetWindowType(
|
|
GTK_WIDGET(window_),
|
|
TabOverviewTypes::WINDOW_TYPE_CHROME_TOPLEVEL,
|
|
NULL);
|
|
}
|
|
#endif
|
|
|
|
gtk_widget_show(GTK_WIDGET(window_));
|
|
}
|
|
|
|
void BrowserWindowGtk::SetBounds(const gfx::Rect& bounds) {
|
|
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);
|
|
gtk_window_resize(window_, width, height);
|
|
}
|
|
|
|
void BrowserWindowGtk::Close() {
|
|
// We're already closing. Do nothing.
|
|
if (!window_)
|
|
return;
|
|
|
|
if (!CanClose())
|
|
return;
|
|
|
|
SaveWindowPosition();
|
|
|
|
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;
|
|
gtk_widget_destroy(window);
|
|
|
|
#if defined(OS_CHROMEOS)
|
|
if (panel_controller_) {
|
|
panel_controller_->Close();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void BrowserWindowGtk::Activate() {
|
|
gtk_window_present(window_);
|
|
}
|
|
|
|
bool BrowserWindowGtk::IsActive() const {
|
|
NOTIMPLEMENTED();
|
|
return true;
|
|
}
|
|
|
|
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::UpdateTitleBar() {
|
|
#if defined(OS_CHROMEOS)
|
|
if (panel_controller_)
|
|
panel_controller_->UpdateTitleBar();
|
|
#endif
|
|
|
|
std::wstring title = browser_->GetCurrentPageTitle();
|
|
gtk_window_set_title(window_, WideToUTF8(title).c_str());
|
|
if (ShouldShowWindowIcon()) {
|
|
// TODO(tc): If we're showing a title bar, we should update the app icon.
|
|
}
|
|
}
|
|
|
|
void BrowserWindowGtk::UpdateDevTools() {
|
|
NOTIMPLEMENTED();
|
|
}
|
|
|
|
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.
|
|
// http://code.google.com/p/chromium/issues/detail?id=9380
|
|
// TODO(willchan): implement this.
|
|
}
|
|
}
|
|
|
|
void BrowserWindowGtk::SetStarredState(bool is_starred) {
|
|
toolbar_->star()->SetStarred(is_starred);
|
|
}
|
|
|
|
gfx::Rect BrowserWindowGtk::GetNormalBounds() const {
|
|
return bounds_;
|
|
}
|
|
|
|
bool BrowserWindowGtk::IsMaximized() const {
|
|
return (state_ & GDK_WINDOW_STATE_MAXIMIZED);
|
|
}
|
|
|
|
void BrowserWindowGtk::SetFullscreen(bool fullscreen) {
|
|
full_screen_ = fullscreen;
|
|
UpdateCustomFrame();
|
|
|
|
if (fullscreen) {
|
|
// These three balanced by ShowSupportedWindowFeatures().
|
|
toolbar_->Hide();
|
|
tabstrip_->Hide();
|
|
bookmark_bar_->Hide(false);
|
|
|
|
gtk_window_fullscreen(window_);
|
|
} else {
|
|
ShowSupportedWindowFeatures();
|
|
|
|
gtk_window_unfullscreen(window_);
|
|
}
|
|
}
|
|
|
|
bool BrowserWindowGtk::IsFullscreen() const {
|
|
return full_screen_;
|
|
}
|
|
|
|
LocationBar* BrowserWindowGtk::GetLocationBar() const {
|
|
return toolbar_->GetLocationBar();
|
|
}
|
|
|
|
void BrowserWindowGtk::SetFocusToLocationBar() {
|
|
GetLocationBar()->FocusLocation();
|
|
}
|
|
|
|
void BrowserWindowGtk::UpdateStopGoState(bool is_loading, bool force) {
|
|
toolbar_->GetGoButton()->ChangeMode(
|
|
is_loading ? GoButtonGtk::MODE_STOP : GoButtonGtk::MODE_GO, force);
|
|
}
|
|
|
|
void BrowserWindowGtk::UpdateToolbar(TabContents* contents,
|
|
bool should_restore_state) {
|
|
toolbar_->UpdateTabContents(contents, should_restore_state);
|
|
}
|
|
|
|
void BrowserWindowGtk::FocusToolbar() {
|
|
NOTIMPLEMENTED();
|
|
}
|
|
|
|
bool BrowserWindowGtk::IsBookmarkBarVisible() const {
|
|
return browser_->SupportsWindowFeature(Browser::FEATURE_BOOKMARKBAR) &&
|
|
bookmark_bar_.get();
|
|
}
|
|
|
|
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::ShowAboutChromeDialog() {
|
|
ShowAboutDialogForProfile(window_, browser_->profile());
|
|
}
|
|
|
|
void BrowserWindowGtk::ShowTaskManager() {
|
|
TaskManagerGtk::Show();
|
|
}
|
|
|
|
void BrowserWindowGtk::ShowBookmarkManager() {
|
|
BookmarkManagerGtk::Show(browser_->profile());
|
|
}
|
|
|
|
void BrowserWindowGtk::ShowBookmarkBubble(const GURL& url,
|
|
bool already_bookmarked) {
|
|
toolbar_->star()->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());
|
|
}
|
|
|
|
void BrowserWindowGtk::ShowSearchEnginesDialog() {
|
|
KeywordEditorView::Show(browser_->profile());
|
|
}
|
|
|
|
void BrowserWindowGtk::ShowPasswordManager() {
|
|
NOTIMPLEMENTED();
|
|
}
|
|
|
|
void BrowserWindowGtk::ShowSelectProfileDialog() {
|
|
NOTIMPLEMENTED();
|
|
}
|
|
|
|
void BrowserWindowGtk::ShowNewProfileDialog() {
|
|
NOTIMPLEMENTED();
|
|
}
|
|
|
|
void BrowserWindowGtk::ShowHTMLDialog(HtmlDialogUIDelegate* delegate,
|
|
gfx::NativeWindow parent_window) {
|
|
NOTIMPLEMENTED();
|
|
}
|
|
|
|
void BrowserWindowGtk::UserChangedTheme() {
|
|
SetBackgroundColor();
|
|
gdk_window_invalidate_rect(GTK_WIDGET(window_)->window,
|
|
>K_WIDGET(window_)->allocation, TRUE);
|
|
|
|
toolbar_->UserChangedTheme();
|
|
GtkThemeProperties properties(browser_->profile());
|
|
bookmark_bar_->UserChangedTheme(&properties);
|
|
status_bubble_->UserChangedTheme(&properties);
|
|
tabstrip_->UserChangedTheme(&properties);
|
|
}
|
|
|
|
int BrowserWindowGtk::GetExtraRenderViewHeight() const {
|
|
int sum = infobar_container_->TotalHeightOfClosingBars();
|
|
if (bookmark_bar_->IsClosing())
|
|
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::ConfirmBrowserCloseWithPendingDownloads() {
|
|
NOTIMPLEMENTED();
|
|
browser_->InProgressDownloadResponse(false);
|
|
}
|
|
|
|
void BrowserWindowGtk::Observe(NotificationType type,
|
|
const NotificationSource& source,
|
|
const NotificationDetails& details) {
|
|
if (type == NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED) {
|
|
MaybeShowBookmarkBar(browser_->GetSelectedTabContents(), true);
|
|
} else if (type == NotificationType::PREF_CHANGED) {
|
|
std::wstring* pref_name = Details<std::wstring>(details).ptr();
|
|
if (*pref_name == prefs::kUseCustomChromeFrame) {
|
|
UpdateCustomFrame();
|
|
} else {
|
|
NOTREACHED() << "Got a pref change notification we didn't register for!";
|
|
}
|
|
} else {
|
|
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);
|
|
}
|
|
|
|
// TODO(estade): this function should probably be unforked from the BrowserView
|
|
// function of the same name by having a shared partial BrowserWindow
|
|
// implementation.
|
|
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);
|
|
|
|
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();
|
|
|
|
// Update all the UI bits.
|
|
UpdateTitleBar();
|
|
toolbar_->SetProfile(new_contents->profile());
|
|
UpdateToolbar(new_contents, true);
|
|
UpdateUIForContents(new_contents);
|
|
}
|
|
|
|
void BrowserWindowGtk::TabStripEmpty() {
|
|
UpdateUIForContents(NULL);
|
|
}
|
|
|
|
void BrowserWindowGtk::MaybeShowBookmarkBar(TabContents* contents,
|
|
bool animate) {
|
|
// Don't change the visibility state when the browser is full screen or if
|
|
// the bookmark bar isn't supported.
|
|
if (full_screen_ || !IsBookmarkBarSupported())
|
|
return;
|
|
|
|
bool show_bar = false;
|
|
|
|
if (browser_->SupportsWindowFeature(Browser::FEATURE_BOOKMARKBAR)
|
|
&& contents) {
|
|
bookmark_bar_->SetProfile(contents->profile());
|
|
bookmark_bar_->SetPageNavigator(contents);
|
|
show_bar = true;
|
|
}
|
|
|
|
if (show_bar) {
|
|
PrefService* prefs = contents->profile()->GetPrefs();
|
|
show_bar = prefs->GetBoolean(prefs::kShowBookmarkBar);
|
|
}
|
|
|
|
if (show_bar) {
|
|
bookmark_bar_->Show(animate);
|
|
} else {
|
|
bookmark_bar_->Hide(animate);
|
|
}
|
|
}
|
|
|
|
void BrowserWindowGtk::UpdateUIForContents(TabContents* contents) {
|
|
MaybeShowBookmarkBar(contents, false);
|
|
}
|
|
|
|
void BrowserWindowGtk::DestroyBrowser() {
|
|
browser_.reset();
|
|
}
|
|
|
|
void BrowserWindowGtk::OnBoundsChanged(const gfx::Rect& bounds) {
|
|
if (bounds_.size() != bounds.size())
|
|
OnSizeChanged(bounds.width(), bounds.height());
|
|
|
|
bounds_ = bounds;
|
|
SaveWindowPosition();
|
|
}
|
|
|
|
void BrowserWindowGtk::OnStateChanged(GdkWindowState state) {
|
|
state_ = state;
|
|
|
|
UpdateWindowShape(bounds_.width(), bounds_.height());
|
|
SaveWindowPosition();
|
|
}
|
|
|
|
bool BrowserWindowGtk::CanClose() const {
|
|
#if defined(OS_CHROMEOS)
|
|
if (drag_active_)
|
|
return false;
|
|
#endif
|
|
|
|
// 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()->empty()) {
|
|
// 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_box_pack_start(GTK_BOX(render_area_vbox_), findbar->widget(),
|
|
FALSE, FALSE, 0);
|
|
gtk_box_reorder_child(GTK_BOX(render_area_vbox_), findbar->widget(), 0);
|
|
}
|
|
|
|
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_data(G_OBJECT(window), kBrowserWindowKey));
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// static
|
|
GtkWindow* BrowserWindowGtk::GetBrowserWindowForXID(XID xid) {
|
|
return BrowserWindowGtk::xid_map_.find(xid)->second;
|
|
}
|
|
|
|
void BrowserWindowGtk::SetGeometryHints() {
|
|
// Allow the user to resize us arbitrarily small.
|
|
GdkGeometry geometry;
|
|
geometry.min_width = 1;
|
|
geometry.min_height = 1;
|
|
gtk_window_set_geometry_hints(window_, NULL, &geometry, GDK_HINT_MIN_SIZE);
|
|
|
|
if (browser_->GetSavedMaximizedState())
|
|
gtk_window_maximize(window_);
|
|
else
|
|
gtk_window_unmaximize(window_);
|
|
|
|
gfx::Rect bounds = browser_->GetSavedWindowBounds();
|
|
// Note that calling SetBounds() here is incorrect, as that sets a forced
|
|
// position on the window and we intentionally *don't* do that. We tested
|
|
// many programs and none of them restored their position on Linux.
|
|
gtk_window_resize(window_, bounds.width(), bounds.height());
|
|
}
|
|
|
|
void BrowserWindowGtk::SetWindowIcon() {
|
|
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
|
|
GList* icon_list = NULL;
|
|
icon_list = g_list_append(icon_list, rb.GetPixbufNamed(IDR_PRODUCT_ICON_32));
|
|
icon_list = g_list_append(icon_list, rb.GetPixbufNamed(IDR_PRODUCT_LOGO_16));
|
|
gtk_window_set_icon_list(window_, icon_list);
|
|
g_list_free(icon_list);
|
|
}
|
|
|
|
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), this);
|
|
g_signal_connect(window_, "unmap",
|
|
G_CALLBACK(MainWindowUnMapped), this);
|
|
g_signal_connect(window_, "key-press-event",
|
|
G_CALLBACK(OnKeyPress), browser_.get());
|
|
g_signal_connect(window_, "motion-notify-event",
|
|
G_CALLBACK(OnMouseMoveEvent), this);
|
|
g_signal_connect(window_, "button-press-event",
|
|
G_CALLBACK(OnButtonPressEvent), this);
|
|
g_signal_connect(window_, "focus-in-event",
|
|
G_CALLBACK(OnFocusIn), browser_.get());
|
|
}
|
|
|
|
void BrowserWindowGtk::InitWidgets() {
|
|
// This vbox encompasses all of the widgets within the browser, including the
|
|
// tabstrip and the content vbox. The vbox is put in a floating container
|
|
// (see gtk_floating_container.h) so we can position the
|
|
// minimize/maximize/close buttons. The floating container is then put in an
|
|
// alignment so we can do custom frame drawing if the user turns of window
|
|
// manager decorations.
|
|
GtkWidget* 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_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(G_OBJECT(window_container_), "expose-event",
|
|
G_CALLBACK(&OnCustomFrameExpose), this);
|
|
gtk_container_add(GTK_CONTAINER(window_container_), window_vbox);
|
|
|
|
tabstrip_.reset(new TabStripGtk(browser_->tabstrip_model()));
|
|
tabstrip_->Init();
|
|
|
|
// Build the titlebar (tabstrip + header space + min/max/close buttons).
|
|
titlebar_.reset(new BrowserTitlebar(this, window_));
|
|
gtk_box_pack_start(GTK_BOX(window_vbox), titlebar_->widget(), FALSE, FALSE,
|
|
0);
|
|
|
|
// The content_vbox_ surrounds the "content": toolbar+bookmarks bar+page.
|
|
content_vbox_ = gtk_vbox_new(FALSE, 0);
|
|
gtk_widget_show(content_vbox_);
|
|
|
|
toolbar_.reset(new BrowserToolbarGtk(browser_.get(), this));
|
|
toolbar_->Init(browser_->profile(), window_);
|
|
toolbar_->AddToolbarToBox(content_vbox_);
|
|
|
|
bookmark_bar_.reset(new BookmarkBarGtk(browser_->profile(), browser_.get(),
|
|
this));
|
|
bookmark_bar_->AddBookmarkbarToBox(content_vbox_);
|
|
|
|
// 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 |event_box|.
|
|
render_area_vbox_ = gtk_vbox_new(FALSE, 0);
|
|
infobar_container_.reset(new InfoBarContainerGtk(this));
|
|
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()));
|
|
contents_container_->AddContainerToBox(render_area_vbox_);
|
|
gtk_widget_show_all(render_area_vbox_);
|
|
|
|
#if defined(OS_CHROMEOS)
|
|
if (browser_->type() == Browser::TYPE_POPUP) {
|
|
toolbar_->Hide();
|
|
// The window manager needs the min size for popups
|
|
gtk_widget_set_size_request(
|
|
GTK_WIDGET(window_), bounds_.width(), bounds_.height());
|
|
}
|
|
#endif
|
|
|
|
// 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();
|
|
|
|
GtkWidget* event_box = gtk_event_box_new();
|
|
gtk_container_add(GTK_CONTAINER(event_box), render_area_vbox_);
|
|
gtk_widget_show(event_box);
|
|
gtk_container_add(GTK_CONTAINER(content_vbox_), event_box);
|
|
gtk_container_add(GTK_CONTAINER(window_vbox), content_vbox_);
|
|
gtk_container_add(GTK_CONTAINER(window_), window_container_);
|
|
gtk_widget_show(window_container_);
|
|
browser_->tabstrip_model()->AddObserver(this);
|
|
}
|
|
|
|
void BrowserWindowGtk::SetBackgroundColor() {
|
|
// TODO(tc): Handle active/inactive colors.
|
|
Profile* profile = browser()->profile();
|
|
ThemeProvider* theme_provider = profile->GetThemeProvider();
|
|
SkColor frame_color;
|
|
if (browser()->profile()->IsOffTheRecord()) {
|
|
frame_color = theme_provider->GetColor(
|
|
BrowserThemeProvider::COLOR_FRAME_INCOGNITO);
|
|
} else {
|
|
frame_color = theme_provider->GetColor(BrowserThemeProvider::COLOR_FRAME);
|
|
}
|
|
|
|
// Paint the frame color on the left, right and bottom.
|
|
GdkColor frame_color_gdk = GDK_COLOR_RGB(SkColorGetR(frame_color),
|
|
SkColorGetG(frame_color), SkColorGetB(frame_color));
|
|
gtk_widget_modify_bg(GTK_WIDGET(window_), GTK_STATE_NORMAL,
|
|
&frame_color_gdk);
|
|
}
|
|
|
|
void BrowserWindowGtk::OnSizeChanged(int width, int height) {
|
|
UpdateWindowShape(width, height);
|
|
}
|
|
|
|
void BrowserWindowGtk::UpdateWindowShape(int width, int height) {
|
|
if (use_custom_frame_.GetValue() && !full_screen_ && !IsMaximized()) {
|
|
// Make the top corners rounded. We set a mask that includes most of the
|
|
// window except for a few pixels in the top two corners.
|
|
GdkRectangle top_rect = { 3, 0, width - 6, 1 };
|
|
GdkRectangle mid_rect = { 1, 1, width - 2, 2 };
|
|
GdkRectangle bot_rect = { 0, 3, width, height - 3 };
|
|
GdkRegion* mask = gdk_region_rectangle(&top_rect);
|
|
gdk_region_union_with_rect(mask, &mid_rect);
|
|
gdk_region_union_with_rect(mask, &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 {
|
|
if (use_custom_frame_.GetValue()) {
|
|
// 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() {
|
|
GtkAccelGroup* accel_group = gtk_accel_group_new();
|
|
gtk_window_add_accel_group(window_, accel_group);
|
|
// Drop the initial ref on |accel_group| so |window_| will own it.
|
|
g_object_unref(accel_group);
|
|
|
|
for (size_t i = 0; i < arraysize(kAcceleratorMap); ++i) {
|
|
gtk_accel_group_connect(
|
|
accel_group,
|
|
kAcceleratorMap[i].keyval,
|
|
kAcceleratorMap[i].modifier_type,
|
|
GtkAccelFlags(0),
|
|
g_cclosure_new(G_CALLBACK(OnGtkAccelerator), this, NULL));
|
|
}
|
|
}
|
|
|
|
void BrowserWindowGtk::UpdateCustomFrame() {
|
|
bool enable = use_custom_frame_.GetValue() && !full_screen_;
|
|
gtk_window_set_decorated(window_, !enable);
|
|
titlebar_->UpdateCustomFrame(enable);
|
|
UpdateWindowShape(bounds_.width(), bounds_.height());
|
|
}
|
|
|
|
void BrowserWindowGtk::SaveWindowPosition() {
|
|
// Browser::SaveWindowPlacement is used for session restore.
|
|
if (browser_->ShouldSaveWindowPlacement())
|
|
browser_->SaveWindowPlacement(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", bounds_.x());
|
|
window_preferences->SetInteger(L"top", bounds_.y());
|
|
window_preferences->SetInteger(L"right", bounds_.right());
|
|
window_preferences->SetInteger(L"bottom", bounds_.bottom());
|
|
window_preferences->SetBoolean(L"maximized", IsMaximized());
|
|
}
|
|
|
|
// static
|
|
gboolean BrowserWindowGtk::OnGtkAccelerator(GtkAccelGroup* accel_group,
|
|
GObject* acceleratable,
|
|
guint keyval,
|
|
GdkModifierType modifier,
|
|
BrowserWindowGtk* browser_window) {
|
|
int command_id = GetCommandId(keyval, modifier);
|
|
browser_window->ExecuteBrowserCommand(command_id);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// static
|
|
gboolean BrowserWindowGtk::OnMouseMoveEvent(GtkWidget* widget,
|
|
GdkEventMotion* event, BrowserWindowGtk* browser) {
|
|
// Get the mouse position relative to |widget|. We can't just use event->x
|
|
// and event->y because that's relative to the gdk window that got the
|
|
// event.
|
|
gint x = 0;
|
|
gint y = 0;
|
|
gtk_widget_get_pointer(widget, &x, &y);
|
|
|
|
// Update the cursor if we're on the custom frame border.
|
|
GdkWindowEdge edge;
|
|
bool has_hit_edge = browser->GetWindowEdge(x, y, &edge);
|
|
GdkCursorType new_cursor = GDK_LAST_CURSOR;
|
|
if (has_hit_edge)
|
|
new_cursor = GdkWindowEdgeToGdkCursorType(edge);
|
|
|
|
GdkCursorType last_cursor = GDK_LAST_CURSOR;
|
|
if (browser->frame_cursor_)
|
|
last_cursor = browser->frame_cursor_->type;
|
|
|
|
if (last_cursor != new_cursor) {
|
|
if (browser->frame_cursor_) {
|
|
gdk_cursor_unref(browser->frame_cursor_);
|
|
browser->frame_cursor_ = NULL;
|
|
}
|
|
if (has_hit_edge) {
|
|
browser->frame_cursor_ = gdk_cursor_new(new_cursor);
|
|
gdk_window_set_cursor(GTK_WIDGET(browser->window_)->window,
|
|
browser->frame_cursor_);
|
|
} else {
|
|
gdk_window_set_cursor(GTK_WIDGET(browser->window_)->window, NULL);
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
// static
|
|
gboolean BrowserWindowGtk::OnButtonPressEvent(GtkWidget* widget,
|
|
GdkEventButton* event, BrowserWindowGtk* browser) {
|
|
// Handle back/forward.
|
|
// TODO(jhawkins): Investigate the possibility of the button numbers being
|
|
// different for other mice.
|
|
if (event->button == 8) {
|
|
browser->browser_->GoBack(CURRENT_TAB);
|
|
return TRUE;
|
|
} else if (event->button == 9) {
|
|
browser->browser_->GoForward(CURRENT_TAB);
|
|
return TRUE;
|
|
}
|
|
|
|
// Handle left and right clicks. In particular, we care about clicks in the
|
|
// custom frame border and clicks in the titlebar.
|
|
GdkWindowEdge edge;
|
|
bool has_hit_edge = browser->GetWindowEdge(static_cast<int>(event->x),
|
|
static_cast<int>(event->y), &edge);
|
|
// Ignore clicks that are in/below the browser toolbar.
|
|
GtkWidget* toolbar = browser->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 = browser->render_area_vbox_;
|
|
}
|
|
gint toolbar_y;
|
|
gtk_widget_get_pointer(toolbar, NULL, &toolbar_y);
|
|
bool has_hit_titlebar = !browser->IsFullscreen() && (toolbar_y < 0)
|
|
&& !has_hit_edge;
|
|
if (event->button == 1) {
|
|
if (GDK_BUTTON_PRESS == event->type) {
|
|
if (has_hit_titlebar) {
|
|
gtk_window_begin_move_drag(browser->window_, event->button,
|
|
event->x_root, event->y_root, event->time);
|
|
return TRUE;
|
|
} else if (has_hit_edge) {
|
|
gtk_window_begin_resize_drag(browser->window_, edge, event->button,
|
|
event->x_root, event->y_root, event->time);
|
|
return TRUE;
|
|
}
|
|
} else if (GDK_2BUTTON_PRESS == event->type) {
|
|
if (has_hit_titlebar) {
|
|
// Maximize/restore on double click.
|
|
if (browser->IsMaximized()) {
|
|
gtk_window_unmaximize(browser->window_);
|
|
} else {
|
|
gtk_window_maximize(browser->window_);
|
|
}
|
|
return TRUE;
|
|
}
|
|
}
|
|
} else if (event->button == 3) {
|
|
if (has_hit_titlebar) {
|
|
browser->titlebar_->ShowContextMenu();
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE; // Continue to propagate the event.
|
|
}
|
|
|
|
// static
|
|
void BrowserWindowGtk::MainWindowMapped(GtkWidget* widget,
|
|
BrowserWindowGtk* window) {
|
|
// 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,
|
|
BrowserWindowGtk* window) {
|
|
// Unmap the X Window ID.
|
|
XID xid = x11_util::GetX11WindowFromGtkWidget(widget);
|
|
BrowserWindowGtk::xid_map_.erase(xid);
|
|
}
|
|
|
|
void BrowserWindowGtk::ExecuteBrowserCommand(int id) {
|
|
if (browser_->command_updater()->IsCommandEnabled(id))
|
|
browser_->ExecuteCommand(id);
|
|
}
|
|
|
|
void BrowserWindowGtk::ShowSupportedWindowFeatures() {
|
|
if (IsTabStripSupported())
|
|
tabstrip_->Show();
|
|
|
|
if (IsToolbarSupported())
|
|
toolbar_->Show();
|
|
|
|
if (IsBookmarkBarSupported())
|
|
MaybeShowBookmarkBar(browser_->GetSelectedTabContents(), false);
|
|
}
|
|
|
|
void BrowserWindowGtk::HideUnsupportedWindowFeatures() {
|
|
if (!IsTabStripSupported())
|
|
tabstrip_->Hide();
|
|
|
|
if (!IsToolbarSupported())
|
|
toolbar_->Hide();
|
|
|
|
if (!IsBookmarkBarSupported())
|
|
bookmark_bar_->Hide(false);
|
|
}
|
|
|
|
bool BrowserWindowGtk::IsTabStripSupported() {
|
|
return browser_->SupportsWindowFeature(Browser::FEATURE_TABSTRIP);
|
|
}
|
|
|
|
bool BrowserWindowGtk::IsToolbarSupported() {
|
|
return browser_->SupportsWindowFeature(Browser::FEATURE_TOOLBAR) ||
|
|
browser_->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR);
|
|
}
|
|
|
|
bool BrowserWindowGtk::IsBookmarkBarSupported() {
|
|
return browser_->SupportsWindowFeature(Browser::FEATURE_BOOKMARKBAR);
|
|
}
|
|
|
|
bool BrowserWindowGtk::GetWindowEdge(int x, int y, GdkWindowEdge* edge) {
|
|
if (!use_custom_frame_.GetValue())
|
|
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();
|
|
}
|