| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/gtk/browser_window_gtk.h" | |
| 6 | |
| 7 #include <gdk/gdkkeysyms.h> | |
| 8 | |
| 9 #include <string> | |
| 10 | |
| 11 #include "app/l10n_util.h" | |
| 12 #include "base/base_paths.h" | |
| 13 #include "base/command_line.h" | |
| 14 #include "base/logging.h" | |
| 15 #include "base/message_loop.h" | |
| 16 #include "base/path_service.h" | |
| 17 #include "base/scoped_ptr.h" | |
| 18 #include "base/singleton.h" | |
| 19 #include "base/string_util.h" | |
| 20 #include "base/time.h" | |
| 21 #include "base/utf_string_conversions.h" | |
| 22 #include "chrome/app/chrome_command_ids.h" | |
| 23 #include "chrome/browser/autocomplete/autocomplete_edit_view.h" | |
| 24 #include "chrome/browser/bookmarks/bookmark_utils.h" | |
| 25 #include "chrome/browser/browser_list.h" | |
| 26 #include "chrome/browser/browser_process.h" | |
| 27 #include "chrome/browser/debugger/devtools_window.h" | |
| 28 #include "chrome/browser/dom_ui/bug_report_ui.h" | |
| 29 #include "chrome/browser/download/download_item_model.h" | |
| 30 #include "chrome/browser/download/download_manager.h" | |
| 31 #include "chrome/browser/gtk/about_chrome_dialog.h" | |
| 32 #include "chrome/browser/gtk/accelerators_gtk.h" | |
| 33 #include "chrome/browser/gtk/bookmark_bar_gtk.h" | |
| 34 #include "chrome/browser/gtk/browser_titlebar.h" | |
| 35 #include "chrome/browser/gtk/browser_toolbar_gtk.h" | |
| 36 #include "chrome/browser/gtk/cairo_cached_surface.h" | |
| 37 #include "chrome/browser/gtk/clear_browsing_data_dialog_gtk.h" | |
| 38 #include "chrome/browser/gtk/collected_cookies_gtk.h" | |
| 39 #include "chrome/browser/gtk/create_application_shortcuts_dialog_gtk.h" | |
| 40 #include "chrome/browser/gtk/download_in_progress_dialog_gtk.h" | |
| 41 #include "chrome/browser/gtk/download_shelf_gtk.h" | |
| 42 #include "chrome/browser/gtk/edit_search_engine_dialog.h" | |
| 43 #include "chrome/browser/gtk/find_bar_gtk.h" | |
| 44 #include "chrome/browser/gtk/fullscreen_exit_bubble_gtk.h" | |
| 45 #include "chrome/browser/gtk/gtk_floating_container.h" | |
| 46 #include "chrome/browser/gtk/gtk_theme_provider.h" | |
| 47 #include "chrome/browser/gtk/gtk_util.h" | |
| 48 #include "chrome/browser/gtk/html_dialog_gtk.h" | |
| 49 #include "chrome/browser/gtk/import_dialog_gtk.h" | |
| 50 #include "chrome/browser/gtk/info_bubble_gtk.h" | |
| 51 #include "chrome/browser/gtk/infobar_container_gtk.h" | |
| 52 #include "chrome/browser/gtk/infobar_gtk.h" | |
| 53 #include "chrome/browser/gtk/keyword_editor_view.h" | |
| 54 #include "chrome/browser/gtk/location_bar_view_gtk.h" | |
| 55 #include "chrome/browser/gtk/nine_box.h" | |
| 56 #include "chrome/browser/gtk/options/content_settings_window_gtk.h" | |
| 57 #include "chrome/browser/gtk/reload_button_gtk.h" | |
| 58 #include "chrome/browser/gtk/repost_form_warning_gtk.h" | |
| 59 #include "chrome/browser/gtk/status_bubble_gtk.h" | |
| 60 #include "chrome/browser/gtk/tab_contents_container_gtk.h" | |
| 61 #include "chrome/browser/gtk/tabs/tab_strip_gtk.h" | |
| 62 #include "chrome/browser/gtk/task_manager_gtk.h" | |
| 63 #include "chrome/browser/gtk/theme_install_bubble_view_gtk.h" | |
| 64 #include "chrome/browser/gtk/update_recommended_dialog.h" | |
| 65 #include "chrome/browser/page_info_window.h" | |
| 66 #include "chrome/browser/prefs/pref_service.h" | |
| 67 #include "chrome/browser/profiles/profile.h" | |
| 68 #include "chrome/browser/tab_contents/tab_contents.h" | |
| 69 #include "chrome/browser/tab_contents/tab_contents_view.h" | |
| 70 #include "chrome/browser/tabs/tab_strip_model.h" | |
| 71 #include "chrome/browser/themes/browser_theme_provider.h" | |
| 72 #include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog_queue.h" | |
| 73 #include "chrome/browser/ui/browser.h" | |
| 74 #include "chrome/browser/ui/find_bar/find_bar_controller.h" | |
| 75 #include "chrome/browser/ui/omnibox/location_bar.h" | |
| 76 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" | |
| 77 #include "chrome/browser/ui/window_sizer.h" | |
| 78 #include "chrome/common/chrome_switches.h" | |
| 79 #include "chrome/common/native_web_keyboard_event.h" | |
| 80 #include "chrome/common/notification_service.h" | |
| 81 #include "chrome/common/pref_names.h" | |
| 82 #include "gfx/gtk_util.h" | |
| 83 #include "gfx/rect.h" | |
| 84 #include "gfx/skia_utils_gtk.h" | |
| 85 #include "grit/app_resources.h" | |
| 86 #include "grit/chromium_strings.h" | |
| 87 #include "grit/generated_resources.h" | |
| 88 #include "grit/theme_resources.h" | |
| 89 #include "ui/base/keycodes/keyboard_codes.h" | |
| 90 | |
| 91 namespace { | |
| 92 | |
| 93 // The number of milliseconds between loading animation frames. | |
| 94 const int kLoadingAnimationFrameTimeMs = 30; | |
| 95 | |
| 96 // Default height of dev tools pane when docked to the browser window. This | |
| 97 // matches the value in Views. | |
| 98 const int kDefaultDevToolsHeight = 200; | |
| 99 | |
| 100 const int kMinDevToolsHeight = 50; | |
| 101 | |
| 102 const char* kBrowserWindowKey = "__BROWSER_WINDOW_GTK__"; | |
| 103 | |
| 104 // The frame border is only visible in restored mode and is hardcoded to 4 px | |
| 105 // on each side regardless of the system window border size. | |
| 106 const int kFrameBorderThickness = 4; | |
| 107 // While resize areas on Windows are normally the same size as the window | |
| 108 // borders, our top area is shrunk by 1 px to make it easier to move the window | |
| 109 // around with our thinner top grabbable strip. (Incidentally, our side and | |
| 110 // bottom resize areas don't match the frame border thickness either -- they | |
| 111 // span the whole nonclient area, so there's no "dead zone" for the mouse.) | |
| 112 const int kTopResizeAdjust = 1; | |
| 113 // In the window corners, the resize areas don't actually expand bigger, but | |
| 114 // the 16 px at the end of each edge triggers diagonal resizing. | |
| 115 const int kResizeAreaCornerSize = 16; | |
| 116 // The thickness of the shadow around the toolbar+web content area. There are | |
| 117 // actually a couple pixels more that should overlap the toolbar and web | |
| 118 // content area, but we don't use those pixels. | |
| 119 const int kContentShadowThickness = 2; | |
| 120 // The offset to the background when the custom frame is off. We want the | |
| 121 // window background to line up with the tab background regardless of whether | |
| 122 // we're in custom frame mode or not. Since themes are designed with the | |
| 123 // custom frame in mind, we need to offset the background when the custom frame | |
| 124 // is off. | |
| 125 const int kCustomFrameBackgroundVerticalOffset = 15; | |
| 126 | |
| 127 // The timeout in milliseconds before we'll get the true window position with | |
| 128 // gtk_window_get_position() after the last GTK configure-event signal. | |
| 129 const int kDebounceTimeoutMilliseconds = 100; | |
| 130 | |
| 131 gboolean MainWindowConfigured(GtkWindow* window, GdkEventConfigure* event, | |
| 132 BrowserWindowGtk* browser_win) { | |
| 133 gfx::Rect bounds = gfx::Rect(event->x, event->y, event->width, event->height); | |
| 134 browser_win->OnBoundsChanged(bounds); | |
| 135 return FALSE; | |
| 136 } | |
| 137 | |
| 138 gboolean MainWindowStateChanged(GtkWindow* window, GdkEventWindowState* event, | |
| 139 BrowserWindowGtk* browser_win) { | |
| 140 browser_win->OnStateChanged(event->new_window_state, event->changed_mask); | |
| 141 return FALSE; | |
| 142 } | |
| 143 | |
| 144 // Callback for the delete event. This event is fired when the user tries to | |
| 145 // close the window (e.g., clicking on the X in the window manager title bar). | |
| 146 gboolean MainWindowDeleteEvent(GtkWidget* widget, GdkEvent* event, | |
| 147 BrowserWindowGtk* window) { | |
| 148 window->Close(); | |
| 149 | |
| 150 // Return true to prevent the gtk window from being destroyed. Close will | |
| 151 // destroy it for us. | |
| 152 return TRUE; | |
| 153 } | |
| 154 | |
| 155 void MainWindowDestroy(GtkWidget* widget, BrowserWindowGtk* window) { | |
| 156 // BUG 8712. When we gtk_widget_destroy() in Close(), this will emit the | |
| 157 // signal right away, and we will be here (while Close() is still in the | |
| 158 // call stack). In order to not reenter Close(), and to also follow the | |
| 159 // expectations of BrowserList, we should run the BrowserWindowGtk destructor | |
| 160 // not now, but after the run loop goes back to process messages. Otherwise | |
| 161 // we will remove ourself from BrowserList while it's being iterated. | |
| 162 // Additionally, now that we know the window is gone, we need to make sure to | |
| 163 // set window_ to NULL, otherwise we will try to close the window again when | |
| 164 // we call Close() in the destructor. | |
| 165 // | |
| 166 // We don't want to use DeleteSoon() here since it won't work on a nested pump | |
| 167 // (like in UI tests). | |
| 168 MessageLoop::current()->PostTask(FROM_HERE, | |
| 169 new DeleteTask<BrowserWindowGtk>(window)); | |
| 170 } | |
| 171 | |
| 172 // Using gtk_window_get_position/size creates a race condition, so only use | |
| 173 // this to get the initial bounds. After window creation, we pick up the | |
| 174 // normal bounds by connecting to the configure-event signal. | |
| 175 gfx::Rect GetInitialWindowBounds(GtkWindow* window) { | |
| 176 gint x, y, width, height; | |
| 177 gtk_window_get_position(window, &x, &y); | |
| 178 gtk_window_get_size(window, &width, &height); | |
| 179 return gfx::Rect(x, y, width, height); | |
| 180 } | |
| 181 | |
| 182 // Get the command ids of the key combinations that are not valid gtk | |
| 183 // accelerators. | |
| 184 int GetCustomCommandId(GdkEventKey* event) { | |
| 185 // Filter modifier to only include accelerator modifiers. | |
| 186 guint modifier = event->state & gtk_accelerator_get_default_mod_mask(); | |
| 187 switch (event->keyval) { | |
| 188 // Gtk doesn't allow GDK_Tab or GDK_ISO_Left_Tab to be an accelerator (see | |
| 189 // gtk_accelerator_valid), so we need to handle these accelerators | |
| 190 // manually. | |
| 191 // Some X clients (e.g. cygwin, NX client, etc.) also send GDK_KP_Tab when | |
| 192 // typing a tab key. We should also handle GDK_KP_Tab for such X clients as | |
| 193 // Firefox does. | |
| 194 case GDK_Tab: | |
| 195 case GDK_ISO_Left_Tab: | |
| 196 case GDK_KP_Tab: | |
| 197 if (GDK_CONTROL_MASK == modifier) { | |
| 198 return IDC_SELECT_NEXT_TAB; | |
| 199 } else if ((GDK_CONTROL_MASK | GDK_SHIFT_MASK) == modifier) { | |
| 200 return IDC_SELECT_PREVIOUS_TAB; | |
| 201 } | |
| 202 break; | |
| 203 | |
| 204 default: | |
| 205 break; | |
| 206 } | |
| 207 return -1; | |
| 208 } | |
| 209 | |
| 210 // Get the command ids of the accelerators that we don't want the native widget | |
| 211 // to be able to override. | |
| 212 int GetPreHandleCommandId(GdkEventKey* event) { | |
| 213 // Filter modifier to only include accelerator modifiers. | |
| 214 guint modifier = event->state & gtk_accelerator_get_default_mod_mask(); | |
| 215 switch (event->keyval) { | |
| 216 case GDK_Page_Down: | |
| 217 if (GDK_CONTROL_MASK == modifier) { | |
| 218 return IDC_SELECT_NEXT_TAB; | |
| 219 } else if ((GDK_CONTROL_MASK | GDK_SHIFT_MASK) == modifier) { | |
| 220 return IDC_MOVE_TAB_NEXT; | |
| 221 } | |
| 222 break; | |
| 223 | |
| 224 case GDK_Page_Up: | |
| 225 if (GDK_CONTROL_MASK == modifier) { | |
| 226 return IDC_SELECT_PREVIOUS_TAB; | |
| 227 } else if ((GDK_CONTROL_MASK | GDK_SHIFT_MASK) == modifier) { | |
| 228 return IDC_MOVE_TAB_PREVIOUS; | |
| 229 } | |
| 230 break; | |
| 231 | |
| 232 default: | |
| 233 break; | |
| 234 } | |
| 235 return -1; | |
| 236 } | |
| 237 | |
| 238 GdkCursorType GdkWindowEdgeToGdkCursorType(GdkWindowEdge edge) { | |
| 239 switch (edge) { | |
| 240 case GDK_WINDOW_EDGE_NORTH_WEST: | |
| 241 return GDK_TOP_LEFT_CORNER; | |
| 242 case GDK_WINDOW_EDGE_NORTH: | |
| 243 return GDK_TOP_SIDE; | |
| 244 case GDK_WINDOW_EDGE_NORTH_EAST: | |
| 245 return GDK_TOP_RIGHT_CORNER; | |
| 246 case GDK_WINDOW_EDGE_WEST: | |
| 247 return GDK_LEFT_SIDE; | |
| 248 case GDK_WINDOW_EDGE_EAST: | |
| 249 return GDK_RIGHT_SIDE; | |
| 250 case GDK_WINDOW_EDGE_SOUTH_WEST: | |
| 251 return GDK_BOTTOM_LEFT_CORNER; | |
| 252 case GDK_WINDOW_EDGE_SOUTH: | |
| 253 return GDK_BOTTOM_SIDE; | |
| 254 case GDK_WINDOW_EDGE_SOUTH_EAST: | |
| 255 return GDK_BOTTOM_RIGHT_CORNER; | |
| 256 default: | |
| 257 NOTREACHED(); | |
| 258 } | |
| 259 return GDK_LAST_CURSOR; | |
| 260 } | |
| 261 | |
| 262 // A helper method for setting the GtkWindow size that should be used in place | |
| 263 // of calling gtk_window_resize directly. This is done to avoid a WM "feature" | |
| 264 // where setting the window size to the monitor size causes the WM to set the | |
| 265 // EWMH for full screen mode. | |
| 266 void SetWindowSize(GtkWindow* window, const gfx::Size& size) { | |
| 267 GdkScreen* screen = gtk_window_get_screen(window); | |
| 268 gint num_monitors = gdk_screen_get_n_monitors(screen); | |
| 269 // Make sure the window doesn't match any monitor size. We compare against | |
| 270 // all monitors because we don't know which monitor the window is going to | |
| 271 // open on (the WM decides that). | |
| 272 for (gint i = 0; i < num_monitors; ++i) { | |
| 273 GdkRectangle monitor_size; | |
| 274 gdk_screen_get_monitor_geometry(screen, i, &monitor_size); | |
| 275 if (gfx::Size(monitor_size.width, monitor_size.height) == size) { | |
| 276 gtk_window_resize(window, size.width(), size.height() - 1); | |
| 277 return; | |
| 278 } | |
| 279 } | |
| 280 gtk_window_resize(window, size.width(), size.height()); | |
| 281 } | |
| 282 | |
| 283 GQuark GetBrowserWindowQuarkKey() { | |
| 284 static GQuark quark = g_quark_from_static_string(kBrowserWindowKey); | |
| 285 return quark; | |
| 286 } | |
| 287 | |
| 288 } // namespace | |
| 289 | |
| 290 std::map<XID, GtkWindow*> BrowserWindowGtk::xid_map_; | |
| 291 | |
| 292 BrowserWindowGtk::BrowserWindowGtk(Browser* browser) | |
| 293 : browser_(browser), | |
| 294 state_(GDK_WINDOW_STATE_WITHDRAWN), | |
| 295 bookmark_bar_is_floating_(false), | |
| 296 frame_cursor_(NULL), | |
| 297 is_active_(true), | |
| 298 last_click_time_(0), | |
| 299 maximize_after_show_(false), | |
| 300 suppress_window_raise_(false), | |
| 301 accel_group_(NULL), | |
| 302 infobar_arrow_model_(this) { | |
| 303 // We register first so that other views like the toolbar can use the | |
| 304 // is_active() function in their ActiveWindowChanged() handlers. | |
| 305 ActiveWindowWatcherX::AddObserver(this); | |
| 306 | |
| 307 use_custom_frame_pref_.Init(prefs::kUseCustomChromeFrame, | |
| 308 browser_->profile()->GetPrefs(), this); | |
| 309 | |
| 310 // In some (older) versions of compiz, raising top-level windows when they | |
| 311 // are partially off-screen causes them to get snapped back on screen, not | |
| 312 // always even on the current virtual desktop. If we are running under | |
| 313 // compiz, suppress such raises, as they are not necessary in compiz anyway. | |
| 314 std::string wm_name; | |
| 315 if (x11_util::GetWindowManagerName(&wm_name) && wm_name == "compiz") | |
| 316 suppress_window_raise_ = true; | |
| 317 | |
| 318 window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); | |
| 319 g_object_set_qdata(G_OBJECT(window_), GetBrowserWindowQuarkKey(), this); | |
| 320 gtk_widget_add_events(GTK_WIDGET(window_), GDK_BUTTON_PRESS_MASK | | |
| 321 GDK_POINTER_MOTION_MASK); | |
| 322 | |
| 323 // Add this window to its own unique window group to allow for | |
| 324 // window-to-parent modality. | |
| 325 gtk_window_group_add_window(gtk_window_group_new(), window_); | |
| 326 g_object_unref(gtk_window_get_group(window_)); | |
| 327 | |
| 328 // For popups, we initialize widgets then set the window geometry, because | |
| 329 // popups need the widgets inited before they can set the window size | |
| 330 // properly. For other windows, we set the geometry first to prevent resize | |
| 331 // flicker. | |
| 332 if (browser_->type() & Browser::TYPE_POPUP) { | |
| 333 InitWidgets(); | |
| 334 SetGeometryHints(); | |
| 335 } else { | |
| 336 SetGeometryHints(); | |
| 337 InitWidgets(); | |
| 338 } | |
| 339 | |
| 340 ConnectAccelerators(); | |
| 341 | |
| 342 // Set the initial background color of widgets. | |
| 343 SetBackgroundColor(); | |
| 344 HideUnsupportedWindowFeatures(); | |
| 345 | |
| 346 registrar_.Add(this, NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED, | |
| 347 NotificationService::AllSources()); | |
| 348 } | |
| 349 | |
| 350 BrowserWindowGtk::~BrowserWindowGtk() { | |
| 351 ActiveWindowWatcherX::RemoveObserver(this); | |
| 352 | |
| 353 browser_->tabstrip_model()->RemoveObserver(this); | |
| 354 } | |
| 355 | |
| 356 gboolean BrowserWindowGtk::OnCustomFrameExpose(GtkWidget* widget, | |
| 357 GdkEventExpose* event) { | |
| 358 // Draw the default background. | |
| 359 cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(widget->window)); | |
| 360 gdk_cairo_rectangle(cr, &event->area); | |
| 361 cairo_clip(cr); | |
| 362 | |
| 363 if (UsingCustomPopupFrame()) { | |
| 364 DrawPopupFrame(cr, widget, event); | |
| 365 } else { | |
| 366 DrawCustomFrame(cr, widget, event); | |
| 367 } | |
| 368 | |
| 369 DrawContentShadow(cr); | |
| 370 | |
| 371 cairo_destroy(cr); | |
| 372 | |
| 373 if (UseCustomFrame() && !IsMaximized()) { | |
| 374 static NineBox custom_frame_border( | |
| 375 IDR_WINDOW_TOP_LEFT_CORNER, | |
| 376 IDR_WINDOW_TOP_CENTER, | |
| 377 IDR_WINDOW_TOP_RIGHT_CORNER, | |
| 378 IDR_WINDOW_LEFT_SIDE, | |
| 379 0, | |
| 380 IDR_WINDOW_RIGHT_SIDE, | |
| 381 IDR_WINDOW_BOTTOM_LEFT_CORNER, | |
| 382 IDR_WINDOW_BOTTOM_CENTER, | |
| 383 IDR_WINDOW_BOTTOM_RIGHT_CORNER); | |
| 384 | |
| 385 custom_frame_border.RenderToWidget(widget); | |
| 386 } | |
| 387 | |
| 388 return FALSE; // Allow subwidgets to paint. | |
| 389 } | |
| 390 | |
| 391 void BrowserWindowGtk::DrawContentShadow(cairo_t* cr) { | |
| 392 // Draw the shadow above the toolbar. Tabs on the tabstrip will draw over us. | |
| 393 GtkThemeProvider* theme_provider = GtkThemeProvider::GetFrom( | |
| 394 browser()->profile()); | |
| 395 int left_x, top_y; | |
| 396 gtk_widget_translate_coordinates(toolbar_->widget(), | |
| 397 GTK_WIDGET(window_), 0, 0, &left_x, | |
| 398 &top_y); | |
| 399 int center_width = window_vbox_->allocation.width; | |
| 400 | |
| 401 CairoCachedSurface* top_center = theme_provider->GetSurfaceNamed( | |
| 402 IDR_CONTENT_TOP_CENTER, GTK_WIDGET(window_)); | |
| 403 CairoCachedSurface* top_right = theme_provider->GetSurfaceNamed( | |
| 404 IDR_CONTENT_TOP_RIGHT_CORNER, GTK_WIDGET(window_)); | |
| 405 CairoCachedSurface* top_left = theme_provider->GetSurfaceNamed( | |
| 406 IDR_CONTENT_TOP_LEFT_CORNER, GTK_WIDGET(window_)); | |
| 407 | |
| 408 int center_left_x = left_x; | |
| 409 if (ShouldDrawContentDropShadow()) { | |
| 410 // Don't draw over the corners. | |
| 411 center_left_x += top_left->Width() - kContentShadowThickness; | |
| 412 center_width -= (top_left->Width() + top_right->Width()); | |
| 413 center_width += 2 * kContentShadowThickness; | |
| 414 } | |
| 415 | |
| 416 top_center->SetSource(cr, center_left_x, top_y - kContentShadowThickness); | |
| 417 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT); | |
| 418 cairo_rectangle(cr, center_left_x, top_y - kContentShadowThickness, | |
| 419 center_width, top_center->Height()); | |
| 420 cairo_fill(cr); | |
| 421 | |
| 422 // Only draw the rest of the shadow if the user has the custom frame enabled | |
| 423 // and the browser is not maximized. | |
| 424 if (!ShouldDrawContentDropShadow()) | |
| 425 return; | |
| 426 | |
| 427 // The top left corner has a width of 3 pixels. On Windows, the last column | |
| 428 // of pixels overlap the toolbar. We just crop it off on Linux. The top | |
| 429 // corners extend to the base of the toolbar (one pixel above the dividing | |
| 430 // line). | |
| 431 int right_x = center_left_x + center_width; | |
| 432 top_left->SetSource( | |
| 433 cr, left_x - kContentShadowThickness, top_y - kContentShadowThickness); | |
| 434 // The toolbar is shorter in location bar only mode so clip the image to the | |
| 435 // height of the toolbar + the amount of shadow above the toolbar. | |
| 436 cairo_rectangle(cr, | |
| 437 left_x - kContentShadowThickness, | |
| 438 top_y - kContentShadowThickness, | |
| 439 top_left->Width(), | |
| 440 top_left->Height()); | |
| 441 cairo_fill(cr); | |
| 442 | |
| 443 // Likewise, we crop off the left column of pixels for the top right corner. | |
| 444 top_right->SetSource(cr, right_x, top_y - kContentShadowThickness); | |
| 445 cairo_rectangle(cr, | |
| 446 right_x, | |
| 447 top_y - kContentShadowThickness, | |
| 448 top_right->Width(), | |
| 449 top_right->Height()); | |
| 450 cairo_fill(cr); | |
| 451 | |
| 452 // Fill in the sides. As above, we only draw 2 of the 3 columns on Linux. | |
| 453 int bottom_y; | |
| 454 gtk_widget_translate_coordinates(window_vbox_, | |
| 455 GTK_WIDGET(window_), | |
| 456 0, window_vbox_->allocation.height, | |
| 457 NULL, &bottom_y); | |
| 458 // |side_y| is where to start drawing the side shadows. The top corners draw | |
| 459 // the sides down to the bottom of the toolbar. | |
| 460 int side_y = top_y - kContentShadowThickness + top_right->Height(); | |
| 461 // |side_height| is how many pixels to draw for the side borders. We do one | |
| 462 // pixel before the bottom of the web contents because that extra pixel is | |
| 463 // drawn by the bottom corners. | |
| 464 int side_height = bottom_y - side_y - 1; | |
| 465 if (side_height > 0) { | |
| 466 CairoCachedSurface* left = theme_provider->GetSurfaceNamed( | |
| 467 IDR_CONTENT_LEFT_SIDE, GTK_WIDGET(window_)); | |
| 468 left->SetSource(cr, left_x - kContentShadowThickness, side_y); | |
| 469 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT); | |
| 470 cairo_rectangle(cr, | |
| 471 left_x - kContentShadowThickness, | |
| 472 side_y, | |
| 473 kContentShadowThickness, | |
| 474 side_height); | |
| 475 cairo_fill(cr); | |
| 476 | |
| 477 CairoCachedSurface* right = theme_provider->GetSurfaceNamed( | |
| 478 IDR_CONTENT_RIGHT_SIDE, GTK_WIDGET(window_)); | |
| 479 int right_side_x = | |
| 480 right_x + top_right->Width() - kContentShadowThickness - 1; | |
| 481 right->SetSource(cr, right_side_x, side_y); | |
| 482 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT); | |
| 483 cairo_rectangle(cr, | |
| 484 right_side_x, | |
| 485 side_y, | |
| 486 kContentShadowThickness, | |
| 487 side_height); | |
| 488 cairo_fill(cr); | |
| 489 } | |
| 490 | |
| 491 // Draw the bottom corners. The bottom corners also draw the bottom row of | |
| 492 // pixels of the side shadows. | |
| 493 CairoCachedSurface* bottom_left = theme_provider->GetSurfaceNamed( | |
| 494 IDR_CONTENT_BOTTOM_LEFT_CORNER, GTK_WIDGET(window_)); | |
| 495 bottom_left->SetSource(cr, left_x - kContentShadowThickness, bottom_y - 1); | |
| 496 cairo_paint(cr); | |
| 497 | |
| 498 CairoCachedSurface* bottom_right = theme_provider->GetSurfaceNamed( | |
| 499 IDR_CONTENT_BOTTOM_RIGHT_CORNER, GTK_WIDGET(window_)); | |
| 500 bottom_right->SetSource(cr, right_x - 1, bottom_y - 1); | |
| 501 cairo_paint(cr); | |
| 502 | |
| 503 // Finally, draw the bottom row. Since we don't overlap the contents, we clip | |
| 504 // the top row of pixels. | |
| 505 CairoCachedSurface* bottom = theme_provider->GetSurfaceNamed( | |
| 506 IDR_CONTENT_BOTTOM_CENTER, GTK_WIDGET(window_)); | |
| 507 bottom->SetSource(cr, left_x + 1, bottom_y - 1); | |
| 508 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT); | |
| 509 cairo_rectangle(cr, | |
| 510 left_x + 1, | |
| 511 bottom_y, | |
| 512 window_vbox_->allocation.width - 2, | |
| 513 kContentShadowThickness); | |
| 514 cairo_fill(cr); | |
| 515 } | |
| 516 | |
| 517 void BrowserWindowGtk::DrawPopupFrame(cairo_t* cr, | |
| 518 GtkWidget* widget, | |
| 519 GdkEventExpose* event) { | |
| 520 GtkThemeProvider* theme_provider = GtkThemeProvider::GetFrom( | |
| 521 browser()->profile()); | |
| 522 | |
| 523 // Like DrawCustomFrame(), except that we use the unthemed resources to draw | |
| 524 // the background. We do this because we can't rely on sane images in the | |
| 525 // theme that we can draw text on. (We tried using the tab background, but | |
| 526 // that has inverse saturation from what the user usually expects). | |
| 527 int image_name = GetThemeFrameResource(); | |
| 528 CairoCachedSurface* surface = theme_provider->GetUnthemedSurfaceNamed( | |
| 529 image_name, widget); | |
| 530 surface->SetSource(cr, 0, GetVerticalOffset()); | |
| 531 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REFLECT); | |
| 532 cairo_rectangle(cr, event->area.x, event->area.y, | |
| 533 event->area.width, event->area.height); | |
| 534 cairo_fill(cr); | |
| 535 } | |
| 536 | |
| 537 void BrowserWindowGtk::DrawCustomFrame(cairo_t* cr, | |
| 538 GtkWidget* widget, | |
| 539 GdkEventExpose* event) { | |
| 540 GtkThemeProvider* theme_provider = GtkThemeProvider::GetFrom( | |
| 541 browser()->profile()); | |
| 542 | |
| 543 int image_name = GetThemeFrameResource(); | |
| 544 | |
| 545 CairoCachedSurface* surface = theme_provider->GetSurfaceNamed( | |
| 546 image_name, widget); | |
| 547 if (event->area.y < surface->Height()) { | |
| 548 surface->SetSource(cr, 0, GetVerticalOffset()); | |
| 549 | |
| 550 // The frame background isn't tiled vertically. | |
| 551 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT); | |
| 552 cairo_rectangle(cr, event->area.x, event->area.y, | |
| 553 event->area.width, surface->Height() - event->area.y); | |
| 554 cairo_fill(cr); | |
| 555 } | |
| 556 | |
| 557 if (theme_provider->HasCustomImage(IDR_THEME_FRAME_OVERLAY) && | |
| 558 !browser()->profile()->IsOffTheRecord()) { | |
| 559 CairoCachedSurface* theme_overlay = theme_provider->GetSurfaceNamed( | |
| 560 IsActive() ? IDR_THEME_FRAME_OVERLAY | |
| 561 : IDR_THEME_FRAME_OVERLAY_INACTIVE, widget); | |
| 562 theme_overlay->SetSource(cr, 0, GetVerticalOffset()); | |
| 563 cairo_paint(cr); | |
| 564 } | |
| 565 } | |
| 566 | |
| 567 int BrowserWindowGtk::GetVerticalOffset() { | |
| 568 return (IsMaximized() || (!UseCustomFrame())) ? | |
| 569 -kCustomFrameBackgroundVerticalOffset : 0; | |
| 570 } | |
| 571 | |
| 572 int BrowserWindowGtk::GetThemeFrameResource() { | |
| 573 bool off_the_record = browser()->profile()->IsOffTheRecord(); | |
| 574 int image_name; | |
| 575 if (IsActive()) { | |
| 576 image_name = off_the_record ? IDR_THEME_FRAME_INCOGNITO : IDR_THEME_FRAME; | |
| 577 } else { | |
| 578 image_name = off_the_record ? IDR_THEME_FRAME_INCOGNITO_INACTIVE : | |
| 579 IDR_THEME_FRAME_INACTIVE; | |
| 580 } | |
| 581 | |
| 582 return image_name; | |
| 583 } | |
| 584 | |
| 585 void BrowserWindowGtk::Show() { | |
| 586 // The Browser associated with this browser window must become the active | |
| 587 // browser at the time Show() is called. This is the natural behaviour under | |
| 588 // Windows, but gtk_widget_show won't show the widget (and therefore won't | |
| 589 // call OnFocusIn()) until we return to the runloop. Therefore any calls to | |
| 590 // BrowserList::GetLastActive() (for example, in bookmark_util), will return | |
| 591 // the previous browser instead if we don't explicitly set it here. | |
| 592 BrowserList::SetLastActive(browser()); | |
| 593 | |
| 594 gtk_window_present(window_); | |
| 595 if (maximize_after_show_) { | |
| 596 gtk_window_maximize(window_); | |
| 597 maximize_after_show_ = false; | |
| 598 } | |
| 599 | |
| 600 // If we have sized the window by setting a size request for the render | |
| 601 // area, then undo it so that the render view can later adjust its own | |
| 602 // size. | |
| 603 gtk_widget_set_size_request(contents_container_->widget(), -1, -1); | |
| 604 } | |
| 605 | |
| 606 void BrowserWindowGtk::SetBoundsImpl(const gfx::Rect& bounds, | |
| 607 bool exterior, | |
| 608 bool move) { | |
| 609 gint x = static_cast<gint>(bounds.x()); | |
| 610 gint y = static_cast<gint>(bounds.y()); | |
| 611 gint width = static_cast<gint>(bounds.width()); | |
| 612 gint height = static_cast<gint>(bounds.height()); | |
| 613 | |
| 614 if (move) | |
| 615 gtk_window_move(window_, x, y); | |
| 616 | |
| 617 if (exterior) { | |
| 618 SetWindowSize(window_, gfx::Size(width, height)); | |
| 619 } else { | |
| 620 gtk_widget_set_size_request(contents_container_->widget(), | |
| 621 width, height); | |
| 622 } | |
| 623 } | |
| 624 | |
| 625 void BrowserWindowGtk::SetBounds(const gfx::Rect& bounds) { | |
| 626 SetBoundsImpl(bounds, true, true); | |
| 627 } | |
| 628 | |
| 629 void BrowserWindowGtk::Close() { | |
| 630 // We're already closing. Do nothing. | |
| 631 if (!window_) | |
| 632 return; | |
| 633 | |
| 634 if (!CanClose()) | |
| 635 return; | |
| 636 | |
| 637 // We're going to destroy the window, make sure the tab strip isn't running | |
| 638 // any animations which may still reference GtkWidgets. | |
| 639 tabstrip_->StopAnimation(); | |
| 640 | |
| 641 SaveWindowPosition(); | |
| 642 | |
| 643 if (accel_group_) { | |
| 644 // Disconnecting the keys we connected to our accelerator group frees the | |
| 645 // closures allocated in ConnectAccelerators. | |
| 646 AcceleratorsGtk* accelerators = AcceleratorsGtk::GetInstance(); | |
| 647 for (AcceleratorsGtk::const_iterator iter = accelerators->begin(); | |
| 648 iter != accelerators->end(); ++iter) { | |
| 649 gtk_accel_group_disconnect_key(accel_group_, | |
| 650 iter->second.GetGdkKeyCode(), | |
| 651 static_cast<GdkModifierType>(iter->second.modifiers())); | |
| 652 } | |
| 653 gtk_window_remove_accel_group(window_, accel_group_); | |
| 654 g_object_unref(accel_group_); | |
| 655 accel_group_ = NULL; | |
| 656 } | |
| 657 | |
| 658 // Cancel any pending callback from the window configure debounce timer. | |
| 659 window_configure_debounce_timer_.Stop(); | |
| 660 | |
| 661 // Likewise for the loading animation. | |
| 662 loading_animation_timer_.Stop(); | |
| 663 | |
| 664 GtkWidget* window = GTK_WIDGET(window_); | |
| 665 // To help catch bugs in any event handlers that might get fired during the | |
| 666 // destruction, set window_ to NULL before any handlers will run. | |
| 667 window_ = NULL; | |
| 668 titlebar_->set_window(NULL); | |
| 669 gtk_widget_destroy(window); | |
| 670 } | |
| 671 | |
| 672 void BrowserWindowGtk::Activate() { | |
| 673 gtk_window_present(window_); | |
| 674 } | |
| 675 | |
| 676 void BrowserWindowGtk::Deactivate() { | |
| 677 gdk_window_lower(GTK_WIDGET(window_)->window); | |
| 678 } | |
| 679 | |
| 680 bool BrowserWindowGtk::IsActive() const { | |
| 681 return is_active_; | |
| 682 } | |
| 683 | |
| 684 void BrowserWindowGtk::FlashFrame() { | |
| 685 // May not be respected by all window managers. | |
| 686 gtk_window_set_urgency_hint(window_, TRUE); | |
| 687 } | |
| 688 | |
| 689 gfx::NativeWindow BrowserWindowGtk::GetNativeHandle() { | |
| 690 return window_; | |
| 691 } | |
| 692 | |
| 693 BrowserWindowTesting* BrowserWindowGtk::GetBrowserWindowTesting() { | |
| 694 NOTIMPLEMENTED(); | |
| 695 return NULL; | |
| 696 } | |
| 697 | |
| 698 StatusBubble* BrowserWindowGtk::GetStatusBubble() { | |
| 699 return status_bubble_.get(); | |
| 700 } | |
| 701 | |
| 702 void BrowserWindowGtk::SelectedTabToolbarSizeChanged(bool is_animating) { | |
| 703 // On Windows, this is used for a performance optimization. | |
| 704 // http://code.google.com/p/chromium/issues/detail?id=12291 | |
| 705 } | |
| 706 | |
| 707 void BrowserWindowGtk::UpdateTitleBar() { | |
| 708 string16 title = browser_->GetWindowTitleForCurrentTab(); | |
| 709 gtk_window_set_title(window_, UTF16ToUTF8(title).c_str()); | |
| 710 if (ShouldShowWindowIcon()) | |
| 711 titlebar_->UpdateTitleAndIcon(); | |
| 712 } | |
| 713 | |
| 714 void BrowserWindowGtk::ShelfVisibilityChanged() { | |
| 715 MaybeShowBookmarkBar(false); | |
| 716 } | |
| 717 | |
| 718 void BrowserWindowGtk::UpdateDevTools() { | |
| 719 UpdateDevToolsForContents( | |
| 720 browser_->GetSelectedTabContents()); | |
| 721 } | |
| 722 | |
| 723 void BrowserWindowGtk::UpdateLoadingAnimations(bool should_animate) { | |
| 724 if (should_animate) { | |
| 725 if (!loading_animation_timer_.IsRunning()) { | |
| 726 // Loads are happening, and the timer isn't running, so start it. | |
| 727 loading_animation_timer_.Start( | |
| 728 base::TimeDelta::FromMilliseconds(kLoadingAnimationFrameTimeMs), this, | |
| 729 &BrowserWindowGtk::LoadingAnimationCallback); | |
| 730 } | |
| 731 } else { | |
| 732 if (loading_animation_timer_.IsRunning()) { | |
| 733 loading_animation_timer_.Stop(); | |
| 734 // Loads are now complete, update the state if a task was scheduled. | |
| 735 LoadingAnimationCallback(); | |
| 736 } | |
| 737 } | |
| 738 } | |
| 739 | |
| 740 void BrowserWindowGtk::LoadingAnimationCallback() { | |
| 741 if (browser_->type() == Browser::TYPE_NORMAL) { | |
| 742 // Loading animations are shown in the tab for tabbed windows. We check the | |
| 743 // browser type instead of calling IsTabStripVisible() because the latter | |
| 744 // will return false for fullscreen windows, but we still need to update | |
| 745 // their animations (so that when they come out of fullscreen mode they'll | |
| 746 // be correct). | |
| 747 tabstrip_->UpdateLoadingAnimations(); | |
| 748 } else if (ShouldShowWindowIcon()) { | |
| 749 // ... or in the window icon area for popups and app windows. | |
| 750 TabContents* tab_contents = browser_->GetSelectedTabContents(); | |
| 751 // GetSelectedTabContents can return NULL for example under Purify when | |
| 752 // the animations are running slowly and this function is called on | |
| 753 // a timer through LoadingAnimationCallback. | |
| 754 titlebar_->UpdateThrobber(tab_contents); | |
| 755 } | |
| 756 } | |
| 757 | |
| 758 void BrowserWindowGtk::SetStarredState(bool is_starred) { | |
| 759 toolbar_->GetLocationBarView()->SetStarred(is_starred); | |
| 760 } | |
| 761 | |
| 762 gfx::Rect BrowserWindowGtk::GetRestoredBounds() const { | |
| 763 return restored_bounds_; | |
| 764 } | |
| 765 | |
| 766 bool BrowserWindowGtk::IsMaximized() const { | |
| 767 return (state_ & GDK_WINDOW_STATE_MAXIMIZED); | |
| 768 } | |
| 769 | |
| 770 bool BrowserWindowGtk::ShouldDrawContentDropShadow() { | |
| 771 return !IsMaximized() && UseCustomFrame(); | |
| 772 } | |
| 773 | |
| 774 void BrowserWindowGtk::SetFullscreen(bool fullscreen) { | |
| 775 // gtk_window_(un)fullscreen asks the window manager to toggle the EWMH | |
| 776 // for fullscreen windows. Not all window managers support this. | |
| 777 if (fullscreen) { | |
| 778 gtk_window_fullscreen(window_); | |
| 779 } else { | |
| 780 // Work around a bug where if we try to unfullscreen, metacity immediately | |
| 781 // fullscreens us again. This is a little flickery and not necessary if | |
| 782 // there's a gnome-panel, but it's not easy to detect whether there's a | |
| 783 // panel or not. | |
| 784 std::string wm_name; | |
| 785 bool unmaximize_before_unfullscreen = IsMaximized() && | |
| 786 x11_util::GetWindowManagerName(&wm_name) && wm_name == "Metacity"; | |
| 787 if (unmaximize_before_unfullscreen) | |
| 788 UnMaximize(); | |
| 789 | |
| 790 gtk_window_unfullscreen(window_); | |
| 791 | |
| 792 if (unmaximize_before_unfullscreen) | |
| 793 gtk_window_maximize(window_); | |
| 794 } | |
| 795 } | |
| 796 | |
| 797 bool BrowserWindowGtk::IsFullscreen() const { | |
| 798 return (state_ & GDK_WINDOW_STATE_FULLSCREEN); | |
| 799 } | |
| 800 | |
| 801 bool BrowserWindowGtk::IsFullscreenBubbleVisible() const { | |
| 802 return fullscreen_exit_bubble_.get() ? true : false; | |
| 803 } | |
| 804 | |
| 805 LocationBar* BrowserWindowGtk::GetLocationBar() const { | |
| 806 return toolbar_->GetLocationBar(); | |
| 807 } | |
| 808 | |
| 809 void BrowserWindowGtk::SetFocusToLocationBar(bool select_all) { | |
| 810 if (!IsFullscreen()) | |
| 811 GetLocationBar()->FocusLocation(select_all); | |
| 812 } | |
| 813 | |
| 814 void BrowserWindowGtk::UpdateReloadStopState(bool is_loading, bool force) { | |
| 815 toolbar_->GetReloadButton()->ChangeMode( | |
| 816 is_loading ? ReloadButtonGtk::MODE_STOP : ReloadButtonGtk::MODE_RELOAD, | |
| 817 force); | |
| 818 } | |
| 819 | |
| 820 void BrowserWindowGtk::UpdateToolbar(TabContentsWrapper* contents, | |
| 821 bool should_restore_state) { | |
| 822 toolbar_->UpdateTabContents(contents->tab_contents(), should_restore_state); | |
| 823 } | |
| 824 | |
| 825 void BrowserWindowGtk::FocusToolbar() { | |
| 826 NOTIMPLEMENTED(); | |
| 827 } | |
| 828 | |
| 829 void BrowserWindowGtk::FocusAppMenu() { | |
| 830 NOTIMPLEMENTED(); | |
| 831 } | |
| 832 | |
| 833 void BrowserWindowGtk::FocusBookmarksToolbar() { | |
| 834 NOTIMPLEMENTED(); | |
| 835 } | |
| 836 | |
| 837 void BrowserWindowGtk::FocusChromeOSStatus() { | |
| 838 NOTIMPLEMENTED(); | |
| 839 } | |
| 840 | |
| 841 void BrowserWindowGtk::RotatePaneFocus(bool forwards) { | |
| 842 NOTIMPLEMENTED(); | |
| 843 } | |
| 844 | |
| 845 bool BrowserWindowGtk::IsBookmarkBarVisible() const { | |
| 846 return browser_->SupportsWindowFeature(Browser::FEATURE_BOOKMARKBAR) && | |
| 847 bookmark_bar_.get() && | |
| 848 browser_->profile()->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar); | |
| 849 } | |
| 850 | |
| 851 bool BrowserWindowGtk::IsBookmarkBarAnimating() const { | |
| 852 if (IsBookmarkBarSupported() && bookmark_bar_->IsAnimating()) | |
| 853 return true; | |
| 854 return false; | |
| 855 } | |
| 856 | |
| 857 bool BrowserWindowGtk::IsTabStripEditable() const { | |
| 858 return !tabstrip()->IsDragSessionActive() && | |
| 859 !tabstrip()->IsActiveDropTarget(); | |
| 860 } | |
| 861 | |
| 862 bool BrowserWindowGtk::IsToolbarVisible() const { | |
| 863 return IsToolbarSupported(); | |
| 864 } | |
| 865 | |
| 866 void BrowserWindowGtk::ConfirmAddSearchProvider(const TemplateURL* template_url, | |
| 867 Profile* profile) { | |
| 868 new EditSearchEngineDialog(window_, template_url, NULL, profile); | |
| 869 } | |
| 870 | |
| 871 void BrowserWindowGtk::ToggleBookmarkBar() { | |
| 872 bookmark_utils::ToggleWhenVisible(browser_->profile()); | |
| 873 } | |
| 874 | |
| 875 views::Window* BrowserWindowGtk::ShowAboutChromeDialog() { | |
| 876 ShowAboutDialogForProfile(window_, browser_->profile()); | |
| 877 return NULL; | |
| 878 } | |
| 879 | |
| 880 void BrowserWindowGtk::ShowUpdateChromeDialog() { | |
| 881 UpdateRecommendedDialog::Show(window_); | |
| 882 } | |
| 883 | |
| 884 void BrowserWindowGtk::ShowTaskManager() { | |
| 885 TaskManagerGtk::Show(); | |
| 886 } | |
| 887 | |
| 888 void BrowserWindowGtk::ShowBookmarkBubble(const GURL& url, | |
| 889 bool already_bookmarked) { | |
| 890 toolbar_->GetLocationBarView()->ShowStarBubble(url, !already_bookmarked); | |
| 891 } | |
| 892 | |
| 893 bool BrowserWindowGtk::IsDownloadShelfVisible() const { | |
| 894 return download_shelf_.get() && download_shelf_->IsShowing(); | |
| 895 } | |
| 896 | |
| 897 DownloadShelf* BrowserWindowGtk::GetDownloadShelf() { | |
| 898 if (!download_shelf_.get()) | |
| 899 download_shelf_.reset(new DownloadShelfGtk(browser_.get(), | |
| 900 render_area_vbox_)); | |
| 901 return download_shelf_.get(); | |
| 902 } | |
| 903 | |
| 904 void BrowserWindowGtk::ShowClearBrowsingDataDialog() { | |
| 905 ClearBrowsingDataDialogGtk::Show(window_, browser_->profile()); | |
| 906 } | |
| 907 | |
| 908 void BrowserWindowGtk::ShowImportDialog() { | |
| 909 ImportDialogGtk::Show(window_, browser_->profile(), ALL); | |
| 910 } | |
| 911 | |
| 912 void BrowserWindowGtk::ShowSearchEnginesDialog() { | |
| 913 KeywordEditorView::Show(browser_->profile()); | |
| 914 } | |
| 915 | |
| 916 void BrowserWindowGtk::ShowPasswordManager() { | |
| 917 NOTIMPLEMENTED(); | |
| 918 } | |
| 919 | |
| 920 void BrowserWindowGtk::ShowRepostFormWarningDialog(TabContents* tab_contents) { | |
| 921 new RepostFormWarningGtk(GetNativeHandle(), tab_contents); | |
| 922 } | |
| 923 | |
| 924 void BrowserWindowGtk::ShowContentSettingsWindow( | |
| 925 ContentSettingsType content_type, | |
| 926 Profile* profile) { | |
| 927 ContentSettingsWindowGtk::Show(GetNativeHandle(), content_type, profile); | |
| 928 } | |
| 929 | |
| 930 void BrowserWindowGtk::ShowCollectedCookiesDialog(TabContents* tab_contents) { | |
| 931 // Deletes itself on close. | |
| 932 new CollectedCookiesGtk(GetNativeHandle(), tab_contents); | |
| 933 } | |
| 934 | |
| 935 void BrowserWindowGtk::ShowProfileErrorDialog(int message_id) { | |
| 936 std::string title = l10n_util::GetStringUTF8(IDS_PRODUCT_NAME); | |
| 937 std::string message = l10n_util::GetStringUTF8(message_id); | |
| 938 GtkWidget* dialog = gtk_message_dialog_new(window_, | |
| 939 static_cast<GtkDialogFlags>(0), GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, | |
| 940 "%s", message.c_str()); | |
| 941 gtk_util::ApplyMessageDialogQuirks(dialog); | |
| 942 gtk_window_set_title(GTK_WINDOW(dialog), title.c_str()); | |
| 943 g_signal_connect(dialog, "response", G_CALLBACK(gtk_widget_destroy), NULL); | |
| 944 gtk_widget_show_all(dialog); | |
| 945 } | |
| 946 | |
| 947 void BrowserWindowGtk::ShowThemeInstallBubble() { | |
| 948 ThemeInstallBubbleViewGtk::Show(window_); | |
| 949 } | |
| 950 | |
| 951 void BrowserWindowGtk::ShowHTMLDialog(HtmlDialogUIDelegate* delegate, | |
| 952 gfx::NativeWindow parent_window) { | |
| 953 HtmlDialogGtk::ShowHtmlDialogGtk(browser_.get(), delegate, parent_window); | |
| 954 } | |
| 955 | |
| 956 void BrowserWindowGtk::UserChangedTheme() { | |
| 957 SetBackgroundColor(); | |
| 958 gdk_window_invalidate_rect(GTK_WIDGET(window_)->window, | |
| 959 >K_WIDGET(window_)->allocation, TRUE); | |
| 960 UpdateWindowShape(bounds_.width(), bounds_.height()); | |
| 961 } | |
| 962 | |
| 963 int BrowserWindowGtk::GetExtraRenderViewHeight() const { | |
| 964 int sum = infobar_container_->TotalHeightOfAnimatingBars(); | |
| 965 if (IsBookmarkBarSupported() && bookmark_bar_->IsAnimating()) | |
| 966 sum += bookmark_bar_->GetHeight(); | |
| 967 if (download_shelf_.get() && download_shelf_->IsClosing()) | |
| 968 sum += download_shelf_->GetHeight(); | |
| 969 return sum; | |
| 970 } | |
| 971 | |
| 972 void BrowserWindowGtk::TabContentsFocused(TabContents* tab_contents) { | |
| 973 NOTIMPLEMENTED(); | |
| 974 } | |
| 975 | |
| 976 void BrowserWindowGtk::ShowPageInfo(Profile* profile, | |
| 977 const GURL& url, | |
| 978 const NavigationEntry::SSLStatus& ssl, | |
| 979 bool show_history) { | |
| 980 browser::ShowPageInfoBubble(window_, profile, url, ssl, show_history); | |
| 981 } | |
| 982 | |
| 983 void BrowserWindowGtk::ShowAppMenu() { | |
| 984 toolbar_->ShowAppMenu(); | |
| 985 } | |
| 986 | |
| 987 bool BrowserWindowGtk::PreHandleKeyboardEvent( | |
| 988 const NativeWebKeyboardEvent& event, bool* is_keyboard_shortcut) { | |
| 989 GdkEventKey* os_event = event.os_event; | |
| 990 | |
| 991 if (!os_event || event.type != WebKit::WebInputEvent::RawKeyDown) | |
| 992 return false; | |
| 993 | |
| 994 // We first find out the browser command associated to the |event|. | |
| 995 // Then if the command is a reserved one, and should be processed immediately | |
| 996 // according to the |event|, the command will be executed immediately. | |
| 997 // Otherwise we just set |*is_keyboard_shortcut| properly and return false. | |
| 998 | |
| 999 // First check if it's a custom accelerator. | |
| 1000 int id = GetCustomCommandId(os_event); | |
| 1001 | |
| 1002 // Then check if it's a predefined accelerator bound to the window. | |
| 1003 if (id == -1) { | |
| 1004 // This piece of code is based on the fact that calling | |
| 1005 // gtk_window_activate_key() method against |window_| may only trigger a | |
| 1006 // browser command execution, by matching a global accelerator | |
| 1007 // defined in above |kAcceleratorMap|. | |
| 1008 // | |
| 1009 // Here we need to retrieve the command id (if any) associated to the | |
| 1010 // keyboard event. Instead of looking up the command id in above | |
| 1011 // |kAcceleratorMap| table by ourselves, we block the command execution of | |
| 1012 // the |browser_| object then send the keyboard event to the |window_| by | |
| 1013 // calling gtk_window_activate_key() method, as if we are activating an | |
| 1014 // accelerator key. Then we can retrieve the command id from the | |
| 1015 // |browser_| object. | |
| 1016 // | |
| 1017 // Pros of this approach: | |
| 1018 // 1. We don't need to care about keyboard layout problem, as | |
| 1019 // gtk_window_activate_key() method handles it for us. | |
| 1020 // | |
| 1021 // Cons: | |
| 1022 // 1. The logic is a little complicated. | |
| 1023 // 2. We should be careful not to introduce any accelerators that trigger | |
| 1024 // customized code instead of browser commands. | |
| 1025 browser_->SetBlockCommandExecution(true); | |
| 1026 gtk_window_activate_key(window_, os_event); | |
| 1027 // We don't need to care about the WindowOpenDisposition value, | |
| 1028 // because all commands executed in this path use the default value. | |
| 1029 id = browser_->GetLastBlockedCommand(NULL); | |
| 1030 browser_->SetBlockCommandExecution(false); | |
| 1031 } | |
| 1032 | |
| 1033 if (id == -1) | |
| 1034 return false; | |
| 1035 | |
| 1036 // Executing the command may cause |this| object to be destroyed. | |
| 1037 if (browser_->IsReservedCommand(id) && !event.match_edit_command) | |
| 1038 return browser_->ExecuteCommandIfEnabled(id); | |
| 1039 | |
| 1040 // The |event| is a keyboard shortcut. | |
| 1041 DCHECK(is_keyboard_shortcut != NULL); | |
| 1042 *is_keyboard_shortcut = true; | |
| 1043 | |
| 1044 return false; | |
| 1045 } | |
| 1046 | |
| 1047 void BrowserWindowGtk::HandleKeyboardEvent( | |
| 1048 const NativeWebKeyboardEvent& event) { | |
| 1049 GdkEventKey* os_event = event.os_event; | |
| 1050 | |
| 1051 if (!os_event || event.type != WebKit::WebInputEvent::RawKeyDown) | |
| 1052 return; | |
| 1053 | |
| 1054 // Handles a key event in following sequence: | |
| 1055 // 1. Our special key accelerators, such as ctrl-tab, etc. | |
| 1056 // 2. Gtk accelerators. | |
| 1057 // This sequence matches the default key press handler of GtkWindow. | |
| 1058 // | |
| 1059 // It's not necessary to care about the keyboard layout, as | |
| 1060 // gtk_window_activate_key() takes care of it automatically. | |
| 1061 int id = GetCustomCommandId(os_event); | |
| 1062 if (id != -1) | |
| 1063 browser_->ExecuteCommandIfEnabled(id); | |
| 1064 else | |
| 1065 gtk_window_activate_key(window_, os_event); | |
| 1066 } | |
| 1067 | |
| 1068 void BrowserWindowGtk::ShowCreateWebAppShortcutsDialog( | |
| 1069 TabContents* tab_contents) { | |
| 1070 CreateWebApplicationShortcutsDialogGtk::Show(window_, tab_contents); | |
| 1071 } | |
| 1072 | |
| 1073 void BrowserWindowGtk::ShowCreateChromeAppShortcutsDialog( | |
| 1074 Profile* profile, const Extension* app) { | |
| 1075 CreateChromeApplicationShortcutsDialogGtk::Show(window_, app); | |
| 1076 } | |
| 1077 | |
| 1078 void BrowserWindowGtk::Cut() { | |
| 1079 gtk_util::DoCut(this); | |
| 1080 } | |
| 1081 | |
| 1082 void BrowserWindowGtk::Copy() { | |
| 1083 gtk_util::DoCopy(this); | |
| 1084 } | |
| 1085 | |
| 1086 void BrowserWindowGtk::Paste() { | |
| 1087 gtk_util::DoPaste(this); | |
| 1088 } | |
| 1089 | |
| 1090 void BrowserWindowGtk::PrepareForInstant() { | |
| 1091 TabContents* contents = contents_container_->GetTabContents(); | |
| 1092 if (contents) | |
| 1093 contents->FadeForInstant(true); | |
| 1094 } | |
| 1095 | |
| 1096 void BrowserWindowGtk::ShowInstant(TabContents* preview_contents) { | |
| 1097 contents_container_->SetPreviewContents(preview_contents); | |
| 1098 MaybeShowBookmarkBar(false); | |
| 1099 | |
| 1100 TabContents* contents = contents_container_->GetTabContents(); | |
| 1101 if (contents) | |
| 1102 contents->CancelInstantFade(); | |
| 1103 } | |
| 1104 | |
| 1105 void BrowserWindowGtk::HideInstant(bool instant_is_active) { | |
| 1106 contents_container_->PopPreviewContents(); | |
| 1107 MaybeShowBookmarkBar(false); | |
| 1108 | |
| 1109 TabContents* contents = contents_container_->GetTabContents(); | |
| 1110 if (contents) { | |
| 1111 if (instant_is_active) | |
| 1112 contents->FadeForInstant(false); | |
| 1113 else | |
| 1114 contents->CancelInstantFade(); | |
| 1115 } | |
| 1116 } | |
| 1117 | |
| 1118 gfx::Rect BrowserWindowGtk::GetInstantBounds() { | |
| 1119 return gtk_util::GetWidgetScreenBounds(contents_container_->widget()); | |
| 1120 } | |
| 1121 | |
| 1122 gfx::Rect BrowserWindowGtk::GrabWindowSnapshot(std::vector<unsigned char>* | |
| 1123 png_representation) { | |
| 1124 x11_util::GrabWindowSnapshot(window_, png_representation); | |
| 1125 return bounds_; | |
| 1126 } | |
| 1127 | |
| 1128 void BrowserWindowGtk::ConfirmBrowserCloseWithPendingDownloads() { | |
| 1129 new DownloadInProgressDialogGtk(browser()); | |
| 1130 } | |
| 1131 | |
| 1132 void BrowserWindowGtk::Observe(NotificationType type, | |
| 1133 const NotificationSource& source, | |
| 1134 const NotificationDetails& details) { | |
| 1135 switch (type.value) { | |
| 1136 case NotificationType::BOOKMARK_BAR_VISIBILITY_PREF_CHANGED: | |
| 1137 MaybeShowBookmarkBar(true); | |
| 1138 break; | |
| 1139 | |
| 1140 case NotificationType::PREF_CHANGED: { | |
| 1141 std::string* pref_name = Details<std::string>(details).ptr(); | |
| 1142 if (*pref_name == prefs::kUseCustomChromeFrame) { | |
| 1143 UpdateCustomFrame(); | |
| 1144 } else { | |
| 1145 NOTREACHED() << "Got pref change notification we didn't register for!"; | |
| 1146 } | |
| 1147 break; | |
| 1148 } | |
| 1149 | |
| 1150 default: | |
| 1151 NOTREACHED() << "Got a notification we didn't register for!"; | |
| 1152 } | |
| 1153 } | |
| 1154 | |
| 1155 void BrowserWindowGtk::TabDetachedAt(TabContentsWrapper* contents, int index) { | |
| 1156 // We use index here rather than comparing |contents| because by this time | |
| 1157 // the model has already removed |contents| from its list, so | |
| 1158 // browser_->GetSelectedTabContents() will return NULL or something else. | |
| 1159 if (index == browser_->tabstrip_model()->selected_index()) | |
| 1160 infobar_container_->ChangeTabContents(NULL); | |
| 1161 contents_container_->DetachTabContents(contents->tab_contents()); | |
| 1162 UpdateDevToolsForContents(NULL); | |
| 1163 } | |
| 1164 | |
| 1165 void BrowserWindowGtk::TabSelectedAt(TabContentsWrapper* old_contents, | |
| 1166 TabContentsWrapper* new_contents, | |
| 1167 int index, | |
| 1168 bool user_gesture) { | |
| 1169 DCHECK(old_contents != new_contents); | |
| 1170 | |
| 1171 if (old_contents && !old_contents->tab_contents()->is_being_destroyed()) | |
| 1172 old_contents->view()->StoreFocus(); | |
| 1173 | |
| 1174 // Update various elements that are interested in knowing the current | |
| 1175 // TabContents. | |
| 1176 infobar_container_->ChangeTabContents(new_contents->tab_contents()); | |
| 1177 contents_container_->SetTabContents(new_contents->tab_contents()); | |
| 1178 UpdateDevToolsForContents(new_contents->tab_contents()); | |
| 1179 | |
| 1180 new_contents->tab_contents()->DidBecomeSelected(); | |
| 1181 // TODO(estade): after we manage browser activation, add a check to make sure | |
| 1182 // we are the active browser before calling RestoreFocus(). | |
| 1183 if (!browser_->tabstrip_model()->closing_all()) { | |
| 1184 new_contents->view()->RestoreFocus(); | |
| 1185 if (new_contents->tab_contents()->find_ui_active()) | |
| 1186 browser_->GetFindBarController()->find_bar()->SetFocusAndSelection(); | |
| 1187 } | |
| 1188 | |
| 1189 // Update all the UI bits. | |
| 1190 UpdateTitleBar(); | |
| 1191 UpdateToolbar(new_contents, true); | |
| 1192 MaybeShowBookmarkBar(false); | |
| 1193 } | |
| 1194 | |
| 1195 void BrowserWindowGtk::ActiveWindowChanged(GdkWindow* active_window) { | |
| 1196 // Do nothing if we're in the process of closing the browser window. | |
| 1197 if (!window_) | |
| 1198 return; | |
| 1199 | |
| 1200 bool is_active = (GTK_WIDGET(window_)->window == active_window); | |
| 1201 bool changed = (is_active != is_active_); | |
| 1202 | |
| 1203 if (is_active && changed) { | |
| 1204 // If there's an app modal dialog (e.g., JS alert), try to redirect | |
| 1205 // the user's attention to the window owning the dialog. | |
| 1206 if (AppModalDialogQueue::GetInstance()->HasActiveDialog()) { | |
| 1207 AppModalDialogQueue::GetInstance()->ActivateModalDialog(); | |
| 1208 return; | |
| 1209 } | |
| 1210 } | |
| 1211 | |
| 1212 is_active_ = is_active; | |
| 1213 if (changed) { | |
| 1214 SetBackgroundColor(); | |
| 1215 gdk_window_invalidate_rect(GTK_WIDGET(window_)->window, | |
| 1216 >K_WIDGET(window_)->allocation, TRUE); | |
| 1217 // For some reason, the above two calls cause the window shape to be | |
| 1218 // lost so reset it. | |
| 1219 UpdateWindowShape(bounds_.width(), bounds_.height()); | |
| 1220 } | |
| 1221 } | |
| 1222 | |
| 1223 void BrowserWindowGtk::MaybeShowBookmarkBar(bool animate) { | |
| 1224 if (!IsBookmarkBarSupported()) | |
| 1225 return; | |
| 1226 | |
| 1227 TabContents* contents = GetDisplayedTabContents(); | |
| 1228 bool show_bar = false; | |
| 1229 | |
| 1230 if (IsBookmarkBarSupported() && contents) { | |
| 1231 bookmark_bar_->SetProfile(contents->profile()); | |
| 1232 bookmark_bar_->SetPageNavigator(contents); | |
| 1233 show_bar = true; | |
| 1234 } | |
| 1235 | |
| 1236 if (show_bar && contents && !contents->ShouldShowBookmarkBar()) { | |
| 1237 PrefService* prefs = contents->profile()->GetPrefs(); | |
| 1238 show_bar = prefs->GetBoolean(prefs::kShowBookmarkBar) && !IsFullscreen(); | |
| 1239 } | |
| 1240 | |
| 1241 if (show_bar) { | |
| 1242 bookmark_bar_->Show(animate); | |
| 1243 } else if (IsFullscreen()) { | |
| 1244 bookmark_bar_->EnterFullscreen(); | |
| 1245 } else { | |
| 1246 bookmark_bar_->Hide(animate); | |
| 1247 } | |
| 1248 } | |
| 1249 | |
| 1250 void BrowserWindowGtk::UpdateDevToolsForContents(TabContents* contents) { | |
| 1251 TabContents* old_devtools = devtools_container_->GetTabContents(); | |
| 1252 TabContents* devtools_contents = contents ? | |
| 1253 DevToolsWindow::GetDevToolsContents(contents) : NULL; | |
| 1254 if (old_devtools == devtools_contents) | |
| 1255 return; | |
| 1256 | |
| 1257 if (old_devtools) | |
| 1258 devtools_container_->DetachTabContents(old_devtools); | |
| 1259 | |
| 1260 devtools_container_->SetTabContents(devtools_contents); | |
| 1261 if (devtools_contents) { | |
| 1262 // TabContentsViewGtk::WasShown is not called when tab contents is shown by | |
| 1263 // anything other than user selecting a Tab. | |
| 1264 // See TabContentsViewWin::OnWindowPosChanged for reference on how it should | |
| 1265 // be implemented. | |
| 1266 devtools_contents->ShowContents(); | |
| 1267 } | |
| 1268 | |
| 1269 bool should_show = old_devtools == NULL && devtools_contents != NULL; | |
| 1270 bool should_hide = old_devtools != NULL && devtools_contents == NULL; | |
| 1271 if (should_show) { | |
| 1272 gtk_widget_show(devtools_container_->widget()); | |
| 1273 } else if (should_hide) { | |
| 1274 // Store split offset when hiding devtools window only. | |
| 1275 gint divider_offset = gtk_paned_get_position(GTK_PANED(contents_split_)); | |
| 1276 g_browser_process->local_state()->SetInteger( | |
| 1277 prefs::kDevToolsSplitLocation, divider_offset); | |
| 1278 gtk_widget_hide(devtools_container_->widget()); | |
| 1279 } | |
| 1280 } | |
| 1281 | |
| 1282 void BrowserWindowGtk::DestroyBrowser() { | |
| 1283 browser_.reset(); | |
| 1284 } | |
| 1285 | |
| 1286 void BrowserWindowGtk::OnBoundsChanged(const gfx::Rect& bounds) { | |
| 1287 GetLocationBar()->location_entry()->ClosePopup(); | |
| 1288 | |
| 1289 TabContents* tab_contents = GetDisplayedTabContents(); | |
| 1290 if (tab_contents) | |
| 1291 tab_contents->WindowMoveOrResizeStarted(); | |
| 1292 | |
| 1293 if (bounds_.size() != bounds.size()) | |
| 1294 OnSizeChanged(bounds.width(), bounds.height()); | |
| 1295 | |
| 1296 // We update |bounds_| but not |restored_bounds_| here. The latter needs | |
| 1297 // to be updated conditionally when the window is non-maximized and non- | |
| 1298 // fullscreen, but whether those state updates have been processed yet is | |
| 1299 // window-manager specific. We update |restored_bounds_| in the debounced | |
| 1300 // handler below, after the window state has been updated. | |
| 1301 bounds_ = bounds; | |
| 1302 | |
| 1303 // When a window is moved or resized, GTK will call MainWindowConfigured() | |
| 1304 // above. The GdkEventConfigure* that it gets doesn't have quite the right | |
| 1305 // coordinates though (they're relative to the drawable window area, rather | |
| 1306 // than any window manager decorations, if enabled), so we need to call | |
| 1307 // gtk_window_get_position() to get the right values. (Otherwise session | |
| 1308 // restore, if enabled, will restore windows to incorrect positions.) That's | |
| 1309 // a round trip to the X server though, so we set a debounce timer and only | |
| 1310 // call it (in OnDebouncedBoundsChanged() below) after we haven't seen a | |
| 1311 // reconfigure event in a short while. | |
| 1312 // We don't use Reset() because the timer may not yet be running. | |
| 1313 // (In that case Stop() is a no-op.) | |
| 1314 window_configure_debounce_timer_.Stop(); | |
| 1315 window_configure_debounce_timer_.Start(base::TimeDelta::FromMilliseconds( | |
| 1316 kDebounceTimeoutMilliseconds), this, | |
| 1317 &BrowserWindowGtk::OnDebouncedBoundsChanged); | |
| 1318 } | |
| 1319 | |
| 1320 void BrowserWindowGtk::OnDebouncedBoundsChanged() { | |
| 1321 gint x, y; | |
| 1322 gtk_window_get_position(window_, &x, &y); | |
| 1323 gfx::Point origin(x, y); | |
| 1324 bounds_.set_origin(origin); | |
| 1325 if (!IsFullscreen() && !IsMaximized()) | |
| 1326 restored_bounds_ = bounds_; | |
| 1327 SaveWindowPosition(); | |
| 1328 } | |
| 1329 | |
| 1330 void BrowserWindowGtk::OnStateChanged(GdkWindowState state, | |
| 1331 GdkWindowState changed_mask) { | |
| 1332 state_ = state; | |
| 1333 | |
| 1334 if (changed_mask & GDK_WINDOW_STATE_FULLSCREEN) { | |
| 1335 bool is_fullscreen = state & GDK_WINDOW_STATE_FULLSCREEN; | |
| 1336 browser_->UpdateCommandsForFullscreenMode(is_fullscreen); | |
| 1337 if (is_fullscreen) { | |
| 1338 UpdateCustomFrame(); | |
| 1339 toolbar_->Hide(); | |
| 1340 tabstrip_->Hide(); | |
| 1341 if (IsBookmarkBarSupported()) | |
| 1342 bookmark_bar_->EnterFullscreen(); | |
| 1343 bool is_kiosk = | |
| 1344 CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode); | |
| 1345 if (!is_kiosk) { | |
| 1346 fullscreen_exit_bubble_.reset(new FullscreenExitBubbleGtk( | |
| 1347 GTK_FLOATING_CONTAINER(render_area_floating_container_))); | |
| 1348 } | |
| 1349 gtk_widget_hide(toolbar_border_); | |
| 1350 } else { | |
| 1351 fullscreen_exit_bubble_.reset(); | |
| 1352 UpdateCustomFrame(); | |
| 1353 ShowSupportedWindowFeatures(); | |
| 1354 } | |
| 1355 } | |
| 1356 | |
| 1357 UpdateWindowShape(bounds_.width(), bounds_.height()); | |
| 1358 SaveWindowPosition(); | |
| 1359 } | |
| 1360 | |
| 1361 void BrowserWindowGtk::UnMaximize() { | |
| 1362 gtk_window_unmaximize(window_); | |
| 1363 | |
| 1364 // It can happen that you end up with a window whose restore size is the same | |
| 1365 // as the size of the screen, so unmaximizing it merely remaximizes it due to | |
| 1366 // the same WM feature that SetWindowSize() works around. We try to detect | |
| 1367 // this and resize the window to work around the issue. | |
| 1368 if (bounds_.size() == restored_bounds_.size()) | |
| 1369 gtk_window_resize(window_, bounds_.width(), bounds_.height() - 1); | |
| 1370 } | |
| 1371 | |
| 1372 bool BrowserWindowGtk::CanClose() const { | |
| 1373 // You cannot close a frame for which there is an active originating drag | |
| 1374 // session. | |
| 1375 if (tabstrip_->IsDragSessionActive()) | |
| 1376 return false; | |
| 1377 | |
| 1378 // Give beforeunload handlers the chance to cancel the close before we hide | |
| 1379 // the window below. | |
| 1380 if (!browser_->ShouldCloseWindow()) | |
| 1381 return false; | |
| 1382 | |
| 1383 if (!browser_->tabstrip_model()->empty()) { | |
| 1384 // Tab strip isn't empty. Hide the window (so it appears to have closed | |
| 1385 // immediately) and close all the tabs, allowing the renderers to shut | |
| 1386 // down. When the tab strip is empty we'll be called back again. | |
| 1387 gtk_widget_hide(GTK_WIDGET(window_)); | |
| 1388 browser_->OnWindowClosing(); | |
| 1389 return false; | |
| 1390 } | |
| 1391 | |
| 1392 // Empty TabStripModel, it's now safe to allow the Window to be closed. | |
| 1393 NotificationService::current()->Notify( | |
| 1394 NotificationType::WINDOW_CLOSED, | |
| 1395 Source<GtkWindow>(window_), | |
| 1396 NotificationService::NoDetails()); | |
| 1397 return true; | |
| 1398 } | |
| 1399 | |
| 1400 bool BrowserWindowGtk::ShouldShowWindowIcon() const { | |
| 1401 return browser_->SupportsWindowFeature(Browser::FEATURE_TITLEBAR); | |
| 1402 } | |
| 1403 | |
| 1404 void BrowserWindowGtk::AddFindBar(FindBarGtk* findbar) { | |
| 1405 gtk_floating_container_add_floating( | |
| 1406 GTK_FLOATING_CONTAINER(render_area_floating_container_), | |
| 1407 findbar->widget()); | |
| 1408 } | |
| 1409 | |
| 1410 void BrowserWindowGtk::ResetCustomFrameCursor() { | |
| 1411 if (!frame_cursor_) | |
| 1412 return; | |
| 1413 | |
| 1414 frame_cursor_ = NULL; | |
| 1415 gdk_window_set_cursor(GTK_WIDGET(window_)->window, NULL); | |
| 1416 } | |
| 1417 | |
| 1418 // static | |
| 1419 BrowserWindowGtk* BrowserWindowGtk::GetBrowserWindowForNativeWindow( | |
| 1420 gfx::NativeWindow window) { | |
| 1421 if (window) { | |
| 1422 return static_cast<BrowserWindowGtk*>( | |
| 1423 g_object_get_qdata(G_OBJECT(window), GetBrowserWindowQuarkKey())); | |
| 1424 } | |
| 1425 | |
| 1426 return NULL; | |
| 1427 } | |
| 1428 | |
| 1429 // static | |
| 1430 GtkWindow* BrowserWindowGtk::GetBrowserWindowForXID(XID xid) { | |
| 1431 std::map<XID, GtkWindow*>::iterator iter = | |
| 1432 BrowserWindowGtk::xid_map_.find(xid); | |
| 1433 return (iter != BrowserWindowGtk::xid_map_.end()) ? iter->second : NULL; | |
| 1434 } | |
| 1435 | |
| 1436 // static | |
| 1437 void BrowserWindowGtk::RegisterUserPrefs(PrefService* prefs) { | |
| 1438 bool custom_frame_default = false; | |
| 1439 // Avoid checking the window manager if we're not connected to an X server (as | |
| 1440 // is the case in Valgrind tests). | |
| 1441 if (x11_util::XDisplayExists() && | |
| 1442 !prefs->HasPrefPath(prefs::kUseCustomChromeFrame)) { | |
| 1443 custom_frame_default = GetCustomFramePrefDefault(); | |
| 1444 } | |
| 1445 prefs->RegisterBooleanPref( | |
| 1446 prefs::kUseCustomChromeFrame, custom_frame_default); | |
| 1447 } | |
| 1448 | |
| 1449 void BrowserWindowGtk::BookmarkBarIsFloating(bool is_floating) { | |
| 1450 bookmark_bar_is_floating_ = is_floating; | |
| 1451 toolbar_->UpdateForBookmarkBarVisibility(is_floating); | |
| 1452 | |
| 1453 // This can be NULL during initialization of the bookmark bar. | |
| 1454 if (bookmark_bar_.get()) | |
| 1455 PlaceBookmarkBar(is_floating); | |
| 1456 } | |
| 1457 | |
| 1458 TabContents* BrowserWindowGtk::GetDisplayedTabContents() { | |
| 1459 return contents_container_->GetVisibleTabContents(); | |
| 1460 } | |
| 1461 | |
| 1462 void BrowserWindowGtk::QueueToolbarRedraw() { | |
| 1463 gtk_widget_queue_draw(toolbar_->widget()); | |
| 1464 } | |
| 1465 | |
| 1466 void BrowserWindowGtk::SetGeometryHints() { | |
| 1467 // If we call gtk_window_maximize followed by gtk_window_present, compiz gets | |
| 1468 // confused and maximizes the window, but doesn't set the | |
| 1469 // GDK_WINDOW_STATE_MAXIMIZED bit. So instead, we keep track of whether to | |
| 1470 // maximize and call it after gtk_window_present. | |
| 1471 maximize_after_show_ = browser_->GetSavedMaximizedState(); | |
| 1472 | |
| 1473 gfx::Rect bounds = browser_->GetSavedWindowBounds(); | |
| 1474 // We don't blindly call SetBounds here: that sets a forced position | |
| 1475 // on the window and we intentionally *don't* do that for normal | |
| 1476 // windows. Most programs do not restore their window position on | |
| 1477 // Linux, instead letting the window manager choose a position. | |
| 1478 // | |
| 1479 // However, in cases like dropping a tab where the bounds are | |
| 1480 // specifically set, we do want to position explicitly. We also | |
| 1481 // force the position as part of session restore, as applications | |
| 1482 // that restore other, similar state (for instance GIMP, audacity, | |
| 1483 // pidgin, dia, and gkrellm) do tend to restore their positions. | |
| 1484 // | |
| 1485 // For popup windows, we assume that if x == y == 0, the opening page | |
| 1486 // did not specify a position. Let the WM position the popup instead. | |
| 1487 bool is_popup = browser_->type() & Browser::TYPE_POPUP; | |
| 1488 bool popup_without_position = is_popup && | |
| 1489 bounds.x() == 0 && bounds.y() == 0; | |
| 1490 bool move = browser_->bounds_overridden() && !popup_without_position; | |
| 1491 SetBoundsImpl(bounds, !is_popup, move); | |
| 1492 } | |
| 1493 | |
| 1494 void BrowserWindowGtk::ConnectHandlersToSignals() { | |
| 1495 g_signal_connect(window_, "delete-event", | |
| 1496 G_CALLBACK(MainWindowDeleteEvent), this); | |
| 1497 g_signal_connect(window_, "destroy", | |
| 1498 G_CALLBACK(MainWindowDestroy), this); | |
| 1499 g_signal_connect(window_, "configure-event", | |
| 1500 G_CALLBACK(MainWindowConfigured), this); | |
| 1501 g_signal_connect(window_, "window-state-event", | |
| 1502 G_CALLBACK(MainWindowStateChanged), this); | |
| 1503 g_signal_connect(window_, "map", | |
| 1504 G_CALLBACK(MainWindowMapped), NULL); | |
| 1505 g_signal_connect(window_, "unmap", | |
| 1506 G_CALLBACK(MainWindowUnMapped), NULL); | |
| 1507 g_signal_connect(window_, "key-press-event", | |
| 1508 G_CALLBACK(OnKeyPressThunk), this); | |
| 1509 g_signal_connect(window_, "motion-notify-event", | |
| 1510 G_CALLBACK(OnMouseMoveEventThunk), this); | |
| 1511 g_signal_connect(window_, "button-press-event", | |
| 1512 G_CALLBACK(OnButtonPressEventThunk), this); | |
| 1513 g_signal_connect(window_, "focus-in-event", | |
| 1514 G_CALLBACK(OnFocusInThunk), this); | |
| 1515 g_signal_connect(window_, "focus-out-event", | |
| 1516 G_CALLBACK(OnFocusOutThunk), this); | |
| 1517 } | |
| 1518 | |
| 1519 void BrowserWindowGtk::InitWidgets() { | |
| 1520 ConnectHandlersToSignals(); | |
| 1521 bounds_ = restored_bounds_ = GetInitialWindowBounds(window_); | |
| 1522 | |
| 1523 // This vbox encompasses all of the widgets within the browser. This is | |
| 1524 // everything except the custom frame border. | |
| 1525 window_vbox_ = gtk_vbox_new(FALSE, 0); | |
| 1526 gtk_widget_show(window_vbox_); | |
| 1527 | |
| 1528 // The window container draws the custom browser frame. | |
| 1529 window_container_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0); | |
| 1530 gtk_widget_set_name(window_container_, "chrome-custom-frame-border"); | |
| 1531 gtk_widget_set_app_paintable(window_container_, TRUE); | |
| 1532 gtk_widget_set_double_buffered(window_container_, FALSE); | |
| 1533 gtk_widget_set_redraw_on_allocate(window_container_, TRUE); | |
| 1534 g_signal_connect(window_container_, "expose-event", | |
| 1535 G_CALLBACK(OnCustomFrameExposeThunk), this); | |
| 1536 gtk_container_add(GTK_CONTAINER(window_container_), window_vbox_); | |
| 1537 | |
| 1538 tabstrip_.reset(new TabStripGtk(browser_->tabstrip_model(), this)); | |
| 1539 tabstrip_->Init(); | |
| 1540 | |
| 1541 // Build the titlebar (tabstrip + header space + min/max/close buttons). | |
| 1542 titlebar_.reset(new BrowserTitlebar(this, window_)); | |
| 1543 | |
| 1544 // Insert the tabstrip into the window. | |
| 1545 gtk_box_pack_start(GTK_BOX(window_vbox_), titlebar_->widget(), FALSE, FALSE, | |
| 1546 0); | |
| 1547 | |
| 1548 toolbar_.reset(new BrowserToolbarGtk(browser_.get(), this)); | |
| 1549 toolbar_->Init(browser_->profile(), window_); | |
| 1550 gtk_box_pack_start(GTK_BOX(window_vbox_), toolbar_->widget(), | |
| 1551 FALSE, FALSE, 0); | |
| 1552 g_signal_connect_after(toolbar_->widget(), "expose-event", | |
| 1553 G_CALLBACK(OnExposeDrawInfobarBitsThunk), this); | |
| 1554 // This vbox surrounds the render area: find bar, info bars and render view. | |
| 1555 // The reason is that this area as a whole needs to be grouped in its own | |
| 1556 // GdkWindow hierarchy so that animations originating inside it (infobar, | |
| 1557 // download shelf, find bar) are all clipped to that area. This is why | |
| 1558 // |render_area_vbox_| is packed in |render_area_event_box_|. | |
| 1559 render_area_vbox_ = gtk_vbox_new(FALSE, 0); | |
| 1560 gtk_widget_set_name(render_area_vbox_, "chrome-render-area-vbox"); | |
| 1561 render_area_floating_container_ = gtk_floating_container_new(); | |
| 1562 gtk_container_add(GTK_CONTAINER(render_area_floating_container_), | |
| 1563 render_area_vbox_); | |
| 1564 | |
| 1565 GtkWidget* location_icon = toolbar_->GetLocationBarView()-> | |
| 1566 location_icon_widget(); | |
| 1567 g_signal_connect(location_icon, "size-allocate", | |
| 1568 G_CALLBACK(OnLocationIconSizeAllocateThunk), this); | |
| 1569 g_signal_connect_after(location_icon, "expose-event", | |
| 1570 G_CALLBACK(OnExposeDrawInfobarBitsThunk), this); | |
| 1571 | |
| 1572 toolbar_border_ = gtk_event_box_new(); | |
| 1573 gtk_box_pack_start(GTK_BOX(render_area_vbox_), | |
| 1574 toolbar_border_, FALSE, FALSE, 0); | |
| 1575 gtk_widget_set_size_request(toolbar_border_, -1, 1); | |
| 1576 gtk_widget_set_no_show_all(toolbar_border_, TRUE); | |
| 1577 g_signal_connect_after(toolbar_border_, "expose-event", | |
| 1578 G_CALLBACK(OnExposeDrawInfobarBitsThunk), this); | |
| 1579 | |
| 1580 if (IsToolbarSupported()) | |
| 1581 gtk_widget_show(toolbar_border_); | |
| 1582 | |
| 1583 infobar_container_.reset(new InfoBarContainerGtk(browser_->profile())); | |
| 1584 gtk_box_pack_start(GTK_BOX(render_area_vbox_), | |
| 1585 infobar_container_->widget(), | |
| 1586 FALSE, FALSE, 0); | |
| 1587 | |
| 1588 status_bubble_.reset(new StatusBubbleGtk(browser_->profile())); | |
| 1589 | |
| 1590 contents_container_.reset(new TabContentsContainerGtk(status_bubble_.get())); | |
| 1591 devtools_container_.reset(new TabContentsContainerGtk(NULL)); | |
| 1592 ViewIDUtil::SetID(devtools_container_->widget(), VIEW_ID_DEV_TOOLS_DOCKED); | |
| 1593 contents_split_ = gtk_vpaned_new(); | |
| 1594 gtk_paned_pack1(GTK_PANED(contents_split_), contents_container_->widget(), | |
| 1595 TRUE, TRUE); | |
| 1596 gtk_paned_pack2(GTK_PANED(contents_split_), devtools_container_->widget(), | |
| 1597 FALSE, TRUE); | |
| 1598 gtk_box_pack_end(GTK_BOX(render_area_vbox_), contents_split_, TRUE, TRUE, 0); | |
| 1599 // Restore split offset. | |
| 1600 int split_offset = g_browser_process->local_state()->GetInteger( | |
| 1601 prefs::kDevToolsSplitLocation); | |
| 1602 if (split_offset != -1) { | |
| 1603 if (split_offset < kMinDevToolsHeight) | |
| 1604 split_offset = kMinDevToolsHeight; | |
| 1605 gtk_paned_set_position(GTK_PANED(contents_split_), split_offset); | |
| 1606 } else { | |
| 1607 gtk_widget_set_size_request(devtools_container_->widget(), -1, | |
| 1608 kDefaultDevToolsHeight); | |
| 1609 } | |
| 1610 gtk_widget_show_all(render_area_floating_container_); | |
| 1611 gtk_widget_hide(devtools_container_->widget()); | |
| 1612 render_area_event_box_ = gtk_event_box_new(); | |
| 1613 // Set a white background so during startup the user sees white in the | |
| 1614 // content area before we get a TabContents in place. | |
| 1615 gtk_widget_modify_bg(render_area_event_box_, GTK_STATE_NORMAL, | |
| 1616 >k_util::kGdkWhite); | |
| 1617 gtk_container_add(GTK_CONTAINER(render_area_event_box_), | |
| 1618 render_area_floating_container_); | |
| 1619 gtk_widget_show(render_area_event_box_); | |
| 1620 gtk_box_pack_end(GTK_BOX(window_vbox_), render_area_event_box_, | |
| 1621 TRUE, TRUE, 0); | |
| 1622 | |
| 1623 if (IsBookmarkBarSupported()) { | |
| 1624 bookmark_bar_.reset(new BookmarkBarGtk(this, | |
| 1625 browser_->profile(), | |
| 1626 browser_.get(), | |
| 1627 tabstrip_.get())); | |
| 1628 PlaceBookmarkBar(false); | |
| 1629 gtk_widget_show(bookmark_bar_->widget()); | |
| 1630 | |
| 1631 g_signal_connect_after(bookmark_bar_->widget(), "expose-event", | |
| 1632 G_CALLBACK(OnBookmarkBarExposeThunk), this); | |
| 1633 g_signal_connect(bookmark_bar_->widget(), "size-allocate", | |
| 1634 G_CALLBACK(OnBookmarkBarSizeAllocateThunk), this); | |
| 1635 } | |
| 1636 | |
| 1637 // We have to realize the window before we try to apply a window shape mask. | |
| 1638 gtk_widget_realize(GTK_WIDGET(window_)); | |
| 1639 state_ = gdk_window_get_state(GTK_WIDGET(window_)->window); | |
| 1640 // Note that calling this the first time is necessary to get the | |
| 1641 // proper control layout. | |
| 1642 UpdateCustomFrame(); | |
| 1643 | |
| 1644 gtk_container_add(GTK_CONTAINER(window_), window_container_); | |
| 1645 gtk_widget_show(window_container_); | |
| 1646 browser_->tabstrip_model()->AddObserver(this); | |
| 1647 } | |
| 1648 | |
| 1649 void BrowserWindowGtk::SetBackgroundColor() { | |
| 1650 Profile* profile = browser()->profile(); | |
| 1651 GtkThemeProvider* theme_provider = GtkThemeProvider::GetFrom(profile); | |
| 1652 int frame_color_id; | |
| 1653 if (UsingCustomPopupFrame()) { | |
| 1654 frame_color_id = BrowserThemeProvider::COLOR_TOOLBAR; | |
| 1655 } else if (IsActive()) { | |
| 1656 frame_color_id = browser()->profile()->IsOffTheRecord() | |
| 1657 ? BrowserThemeProvider::COLOR_FRAME_INCOGNITO | |
| 1658 : BrowserThemeProvider::COLOR_FRAME; | |
| 1659 } else { | |
| 1660 frame_color_id = browser()->profile()->IsOffTheRecord() | |
| 1661 ? BrowserThemeProvider::COLOR_FRAME_INCOGNITO_INACTIVE | |
| 1662 : BrowserThemeProvider::COLOR_FRAME_INACTIVE; | |
| 1663 } | |
| 1664 | |
| 1665 SkColor frame_color = theme_provider->GetColor(frame_color_id); | |
| 1666 | |
| 1667 // Paint the frame color on the left, right and bottom. | |
| 1668 GdkColor frame_color_gdk = gfx::SkColorToGdkColor(frame_color); | |
| 1669 gtk_widget_modify_bg(GTK_WIDGET(window_), GTK_STATE_NORMAL, | |
| 1670 &frame_color_gdk); | |
| 1671 | |
| 1672 // Set the color of the dev tools divider. | |
| 1673 gtk_widget_modify_bg(contents_split_, GTK_STATE_NORMAL, &frame_color_gdk); | |
| 1674 | |
| 1675 // When the cursor is over the divider, GTK+ normally lightens the background | |
| 1676 // color by 1.3 (see LIGHTNESS_MULT in gtkstyle.c). Since we're setting the | |
| 1677 // color, override the prelight also. | |
| 1678 color_utils::HSL hsl = { -1, 0.5, 0.65 }; | |
| 1679 SkColor frame_prelight_color = color_utils::HSLShift(frame_color, hsl); | |
| 1680 GdkColor frame_prelight_color_gdk = | |
| 1681 gfx::SkColorToGdkColor(frame_prelight_color); | |
| 1682 gtk_widget_modify_bg(contents_split_, GTK_STATE_PRELIGHT, | |
| 1683 &frame_prelight_color_gdk); | |
| 1684 | |
| 1685 GdkColor border_color = theme_provider->GetBorderColor(); | |
| 1686 gtk_widget_modify_bg(toolbar_border_, GTK_STATE_NORMAL, &border_color); | |
| 1687 } | |
| 1688 | |
| 1689 void BrowserWindowGtk::OnSizeChanged(int width, int height) { | |
| 1690 UpdateWindowShape(width, height); | |
| 1691 } | |
| 1692 | |
| 1693 void BrowserWindowGtk::UpdateWindowShape(int width, int height) { | |
| 1694 if (UseCustomFrame() && !IsFullscreen() && !IsMaximized()) { | |
| 1695 // Make the corners rounded. We set a mask that includes most of the | |
| 1696 // window except for a few pixels in each corner. | |
| 1697 GdkRectangle top_top_rect = { 3, 0, width - 6, 1 }; | |
| 1698 GdkRectangle top_mid_rect = { 1, 1, width - 2, 2 }; | |
| 1699 GdkRectangle mid_rect = { 0, 3, width, height - 6 }; | |
| 1700 // The bottom two rects are mirror images of the top two rects. | |
| 1701 GdkRectangle bot_mid_rect = top_mid_rect; | |
| 1702 bot_mid_rect.y = height - 3; | |
| 1703 GdkRectangle bot_bot_rect = top_top_rect; | |
| 1704 bot_bot_rect.y = height - 1; | |
| 1705 GdkRegion* mask = gdk_region_rectangle(&top_top_rect); | |
| 1706 gdk_region_union_with_rect(mask, &top_mid_rect); | |
| 1707 gdk_region_union_with_rect(mask, &mid_rect); | |
| 1708 gdk_region_union_with_rect(mask, &bot_mid_rect); | |
| 1709 gdk_region_union_with_rect(mask, &bot_bot_rect); | |
| 1710 gdk_window_shape_combine_region(GTK_WIDGET(window_)->window, mask, 0, 0); | |
| 1711 gdk_region_destroy(mask); | |
| 1712 gtk_alignment_set_padding(GTK_ALIGNMENT(window_container_), 1, | |
| 1713 kFrameBorderThickness, kFrameBorderThickness, kFrameBorderThickness); | |
| 1714 } else { | |
| 1715 // XFCE disables the system decorations if there's an xshape set. | |
| 1716 if (UseCustomFrame()) { | |
| 1717 // Disable rounded corners. Simply passing in a NULL region doesn't | |
| 1718 // seem to work on KWin, so manually set the shape to the whole window. | |
| 1719 GdkRectangle rect = { 0, 0, width, height }; | |
| 1720 GdkRegion* mask = gdk_region_rectangle(&rect); | |
| 1721 gdk_window_shape_combine_region(GTK_WIDGET(window_)->window, mask, 0, 0); | |
| 1722 gdk_region_destroy(mask); | |
| 1723 } else { | |
| 1724 gdk_window_shape_combine_region(GTK_WIDGET(window_)->window, NULL, 0, 0); | |
| 1725 } | |
| 1726 gtk_alignment_set_padding(GTK_ALIGNMENT(window_container_), 0, 0, 0, 0); | |
| 1727 } | |
| 1728 } | |
| 1729 | |
| 1730 void BrowserWindowGtk::ConnectAccelerators() { | |
| 1731 accel_group_ = gtk_accel_group_new(); | |
| 1732 gtk_window_add_accel_group(window_, accel_group_); | |
| 1733 | |
| 1734 AcceleratorsGtk* accelerators = AcceleratorsGtk::GetInstance(); | |
| 1735 for (AcceleratorsGtk::const_iterator iter = accelerators->begin(); | |
| 1736 iter != accelerators->end(); ++iter) { | |
| 1737 gtk_accel_group_connect( | |
| 1738 accel_group_, | |
| 1739 iter->second.GetGdkKeyCode(), | |
| 1740 static_cast<GdkModifierType>(iter->second.modifiers()), | |
| 1741 GtkAccelFlags(0), | |
| 1742 g_cclosure_new(G_CALLBACK(OnGtkAccelerator), | |
| 1743 GINT_TO_POINTER(iter->first), NULL)); | |
| 1744 } | |
| 1745 } | |
| 1746 | |
| 1747 void BrowserWindowGtk::UpdateCustomFrame() { | |
| 1748 gtk_window_set_decorated(window_, !UseCustomFrame()); | |
| 1749 titlebar_->UpdateCustomFrame(UseCustomFrame() && !IsFullscreen()); | |
| 1750 UpdateWindowShape(bounds_.width(), bounds_.height()); | |
| 1751 } | |
| 1752 | |
| 1753 void BrowserWindowGtk::SaveWindowPosition() { | |
| 1754 // Browser::SaveWindowPlacement is used for session restore. | |
| 1755 if (browser_->ShouldSaveWindowPlacement()) | |
| 1756 browser_->SaveWindowPlacement(restored_bounds_, IsMaximized()); | |
| 1757 | |
| 1758 // We also need to save the placement for startup. | |
| 1759 // This is a web of calls between views and delegates on Windows, but the | |
| 1760 // crux of the logic follows. See also cocoa/browser_window_controller.mm. | |
| 1761 if (!g_browser_process->local_state()) | |
| 1762 return; | |
| 1763 | |
| 1764 std::string window_name = browser_->GetWindowPlacementKey(); | |
| 1765 DictionaryValue* window_preferences = | |
| 1766 g_browser_process->local_state()->GetMutableDictionary( | |
| 1767 window_name.c_str()); | |
| 1768 // Note that we store left/top for consistency with Windows, but that we | |
| 1769 // *don't* obey them; we only use them for computing width/height. See | |
| 1770 // comments in SetGeometryHints(). | |
| 1771 window_preferences->SetInteger("left", restored_bounds_.x()); | |
| 1772 window_preferences->SetInteger("top", restored_bounds_.y()); | |
| 1773 window_preferences->SetInteger("right", restored_bounds_.right()); | |
| 1774 window_preferences->SetInteger("bottom", restored_bounds_.bottom()); | |
| 1775 window_preferences->SetBoolean("maximized", IsMaximized()); | |
| 1776 | |
| 1777 scoped_ptr<WindowSizer::MonitorInfoProvider> monitor_info_provider( | |
| 1778 WindowSizer::CreateDefaultMonitorInfoProvider()); | |
| 1779 gfx::Rect work_area( | |
| 1780 monitor_info_provider->GetMonitorWorkAreaMatching(restored_bounds_)); | |
| 1781 window_preferences->SetInteger("work_area_left", work_area.x()); | |
| 1782 window_preferences->SetInteger("work_area_top", work_area.y()); | |
| 1783 window_preferences->SetInteger("work_area_right", work_area.right()); | |
| 1784 window_preferences->SetInteger("work_area_bottom", work_area.bottom()); | |
| 1785 } | |
| 1786 | |
| 1787 void BrowserWindowGtk::SetInfoBarShowing(InfoBar* bar, bool animate) { | |
| 1788 infobar_arrow_model_.ShowArrowFor(bar, animate); | |
| 1789 } | |
| 1790 | |
| 1791 void BrowserWindowGtk::PaintStateChanged() { | |
| 1792 InvalidateInfoBarBits(); | |
| 1793 } | |
| 1794 | |
| 1795 void BrowserWindowGtk::InvalidateInfoBarBits() { | |
| 1796 gtk_widget_queue_draw(toolbar_border_); | |
| 1797 gtk_widget_queue_draw(toolbar_->widget()); | |
| 1798 if (bookmark_bar_.get() && !bookmark_bar_is_floating_) | |
| 1799 gtk_widget_queue_draw(bookmark_bar_->widget()); | |
| 1800 } | |
| 1801 | |
| 1802 int BrowserWindowGtk::GetXPositionOfLocationIcon(GtkWidget* relative_to) { | |
| 1803 GtkWidget* location_icon = toolbar_->GetLocationBarView()-> | |
| 1804 location_icon_widget(); | |
| 1805 int x = 0; | |
| 1806 gtk_widget_translate_coordinates( | |
| 1807 location_icon, relative_to, | |
| 1808 (location_icon->allocation.width + 1) / 2, | |
| 1809 0, &x, NULL); | |
| 1810 | |
| 1811 if (GTK_WIDGET_NO_WINDOW(relative_to)) | |
| 1812 x += relative_to->allocation.x; | |
| 1813 | |
| 1814 return x; | |
| 1815 } | |
| 1816 | |
| 1817 void BrowserWindowGtk::OnLocationIconSizeAllocate(GtkWidget* sender, | |
| 1818 GtkAllocation* allocation) { | |
| 1819 // The position of the arrow may have changed, so we'll have to redraw it. | |
| 1820 InvalidateInfoBarBits(); | |
| 1821 } | |
| 1822 | |
| 1823 gboolean BrowserWindowGtk::OnExposeDrawInfobarBits(GtkWidget* sender, | |
| 1824 GdkEventExpose* expose) { | |
| 1825 if (!infobar_arrow_model_.NeedToDrawInfoBarArrow()) | |
| 1826 return FALSE; | |
| 1827 | |
| 1828 int x = GetXPositionOfLocationIcon(sender); | |
| 1829 | |
| 1830 gfx::Rect toolbar_border(toolbar_border_->allocation); | |
| 1831 int y = 0; | |
| 1832 gtk_widget_translate_coordinates(toolbar_border_, sender, | |
| 1833 0, toolbar_border.bottom(), | |
| 1834 NULL, &y); | |
| 1835 if (GTK_WIDGET_NO_WINDOW(sender)) | |
| 1836 y += sender->allocation.y; | |
| 1837 | |
| 1838 Profile* profile = browser()->profile(); | |
| 1839 infobar_arrow_model_.Paint( | |
| 1840 sender, expose, gfx::Point(x, y), | |
| 1841 GtkThemeProvider::GetFrom(profile)->GetBorderColor()); | |
| 1842 return FALSE; | |
| 1843 } | |
| 1844 | |
| 1845 gboolean BrowserWindowGtk::OnBookmarkBarExpose(GtkWidget* sender, | |
| 1846 GdkEventExpose* expose) { | |
| 1847 if (!infobar_arrow_model_.NeedToDrawInfoBarArrow()) | |
| 1848 return FALSE; | |
| 1849 | |
| 1850 if (bookmark_bar_is_floating_) | |
| 1851 return FALSE; | |
| 1852 | |
| 1853 return OnExposeDrawInfobarBits(sender, expose); | |
| 1854 } | |
| 1855 | |
| 1856 void BrowserWindowGtk::OnBookmarkBarSizeAllocate(GtkWidget* sender, | |
| 1857 GtkAllocation* allocation) { | |
| 1858 // The size of the bookmark bar affects how the infobar arrow is drawn on | |
| 1859 // the toolbar. | |
| 1860 if (infobar_arrow_model_.NeedToDrawInfoBarArrow()) | |
| 1861 gtk_widget_queue_draw(toolbar_->widget()); | |
| 1862 } | |
| 1863 | |
| 1864 // static | |
| 1865 gboolean BrowserWindowGtk::OnGtkAccelerator(GtkAccelGroup* accel_group, | |
| 1866 GObject* acceleratable, | |
| 1867 guint keyval, | |
| 1868 GdkModifierType modifier, | |
| 1869 void* user_data) { | |
| 1870 int command_id = GPOINTER_TO_INT(user_data); | |
| 1871 BrowserWindowGtk* browser_window = | |
| 1872 GetBrowserWindowForNativeWindow(GTK_WINDOW(acceleratable)); | |
| 1873 DCHECK(browser_window != NULL); | |
| 1874 return browser_window->browser()->ExecuteCommandIfEnabled(command_id); | |
| 1875 } | |
| 1876 | |
| 1877 // Let the focused widget have first crack at the key event so we don't | |
| 1878 // override their accelerators. | |
| 1879 gboolean BrowserWindowGtk::OnKeyPress(GtkWidget* widget, GdkEventKey* event) { | |
| 1880 // If a widget besides the native view is focused, we have to try to handle | |
| 1881 // the custom accelerators before letting it handle them. | |
| 1882 TabContents* current_tab_contents = | |
| 1883 browser()->GetSelectedTabContents(); | |
| 1884 // The current tab might not have a render view if it crashed. | |
| 1885 if (!current_tab_contents || !current_tab_contents->GetContentNativeView() || | |
| 1886 !gtk_widget_is_focus(current_tab_contents->GetContentNativeView())) { | |
| 1887 int command_id = GetCustomCommandId(event); | |
| 1888 if (command_id == -1) | |
| 1889 command_id = GetPreHandleCommandId(event); | |
| 1890 | |
| 1891 if (command_id != -1 && browser_->ExecuteCommandIfEnabled(command_id)) | |
| 1892 return TRUE; | |
| 1893 | |
| 1894 // Propagate the key event to child widget first, so we don't override their | |
| 1895 // accelerators. | |
| 1896 if (!gtk_window_propagate_key_event(GTK_WINDOW(widget), event)) { | |
| 1897 if (!gtk_window_activate_key(GTK_WINDOW(widget), event)) { | |
| 1898 gtk_bindings_activate_event(GTK_OBJECT(widget), event); | |
| 1899 } | |
| 1900 } | |
| 1901 } else { | |
| 1902 bool rv = gtk_window_propagate_key_event(GTK_WINDOW(widget), event); | |
| 1903 DCHECK(rv); | |
| 1904 } | |
| 1905 | |
| 1906 // Prevents the default handler from handling this event. | |
| 1907 return TRUE; | |
| 1908 } | |
| 1909 | |
| 1910 gboolean BrowserWindowGtk::OnMouseMoveEvent(GtkWidget* widget, | |
| 1911 GdkEventMotion* event) { | |
| 1912 // This method is used to update the mouse cursor when over the edge of the | |
| 1913 // custom frame. If the custom frame is off or we're over some other widget, | |
| 1914 // do nothing. | |
| 1915 if (!UseCustomFrame() || event->window != widget->window) { | |
| 1916 // Reset the cursor. | |
| 1917 if (frame_cursor_) { | |
| 1918 frame_cursor_ = NULL; | |
| 1919 gdk_window_set_cursor(GTK_WIDGET(window_)->window, NULL); | |
| 1920 } | |
| 1921 return FALSE; | |
| 1922 } | |
| 1923 | |
| 1924 // Update the cursor if we're on the custom frame border. | |
| 1925 GdkWindowEdge edge; | |
| 1926 bool has_hit_edge = GetWindowEdge(static_cast<int>(event->x), | |
| 1927 static_cast<int>(event->y), &edge); | |
| 1928 GdkCursorType new_cursor = GDK_LAST_CURSOR; | |
| 1929 if (has_hit_edge) | |
| 1930 new_cursor = GdkWindowEdgeToGdkCursorType(edge); | |
| 1931 | |
| 1932 GdkCursorType last_cursor = GDK_LAST_CURSOR; | |
| 1933 if (frame_cursor_) | |
| 1934 last_cursor = frame_cursor_->type; | |
| 1935 | |
| 1936 if (last_cursor != new_cursor) { | |
| 1937 if (has_hit_edge) { | |
| 1938 frame_cursor_ = gfx::GetCursor(new_cursor); | |
| 1939 } else { | |
| 1940 frame_cursor_ = NULL; | |
| 1941 } | |
| 1942 gdk_window_set_cursor(GTK_WIDGET(window_)->window, frame_cursor_); | |
| 1943 } | |
| 1944 return FALSE; | |
| 1945 } | |
| 1946 | |
| 1947 gboolean BrowserWindowGtk::OnButtonPressEvent(GtkWidget* widget, | |
| 1948 GdkEventButton* event) { | |
| 1949 // Handle back/forward. | |
| 1950 if (event->type == GDK_BUTTON_PRESS) { | |
| 1951 if (event->button == 8) { | |
| 1952 browser_->GoBack(CURRENT_TAB); | |
| 1953 return TRUE; | |
| 1954 } else if (event->button == 9) { | |
| 1955 browser_->GoForward(CURRENT_TAB); | |
| 1956 return TRUE; | |
| 1957 } | |
| 1958 } | |
| 1959 | |
| 1960 // Handle left, middle and right clicks. In particular, we care about clicks | |
| 1961 // in the custom frame border and clicks in the titlebar. | |
| 1962 | |
| 1963 // Make the button press coordinate relative to the browser window. | |
| 1964 int win_x, win_y; | |
| 1965 gdk_window_get_origin(GTK_WIDGET(window_)->window, &win_x, &win_y); | |
| 1966 | |
| 1967 GdkWindowEdge edge; | |
| 1968 gfx::Point point(static_cast<int>(event->x_root - win_x), | |
| 1969 static_cast<int>(event->y_root - win_y)); | |
| 1970 bool has_hit_edge = GetWindowEdge(point.x(), point.y(), &edge); | |
| 1971 | |
| 1972 // Ignore clicks that are in/below the browser toolbar. | |
| 1973 GtkWidget* toolbar = toolbar_->widget(); | |
| 1974 if (!GTK_WIDGET_VISIBLE(toolbar)) { | |
| 1975 // If the toolbar is not showing, use the location of web contents as the | |
| 1976 // boundary of where to ignore clicks. | |
| 1977 toolbar = render_area_vbox_; | |
| 1978 } | |
| 1979 gint toolbar_y; | |
| 1980 gtk_widget_get_pointer(toolbar, NULL, &toolbar_y); | |
| 1981 bool has_hit_titlebar = !IsFullscreen() && (toolbar_y < 0) | |
| 1982 && !has_hit_edge; | |
| 1983 if (event->button == 1) { | |
| 1984 if (GDK_BUTTON_PRESS == event->type) { | |
| 1985 guint32 last_click_time = last_click_time_; | |
| 1986 gfx::Point last_click_position = last_click_position_; | |
| 1987 last_click_time_ = event->time; | |
| 1988 last_click_position_ = gfx::Point(static_cast<int>(event->x), | |
| 1989 static_cast<int>(event->y)); | |
| 1990 | |
| 1991 // Raise the window after a click on either the titlebar or the border to | |
| 1992 // match the behavior of most window managers, unless that behavior has | |
| 1993 // been suppressed. | |
| 1994 if ((has_hit_titlebar || has_hit_edge) && !suppress_window_raise_) | |
| 1995 gdk_window_raise(GTK_WIDGET(window_)->window); | |
| 1996 | |
| 1997 if (has_hit_titlebar) { | |
| 1998 // We want to start a move when the user single clicks, but not start a | |
| 1999 // move when the user double clicks. However, a double click sends the | |
| 2000 // following GDK events: GDK_BUTTON_PRESS, GDK_BUTTON_RELEASE, | |
| 2001 // GDK_BUTTON_PRESS, GDK_2BUTTON_PRESS, GDK_BUTTON_RELEASE. If we | |
| 2002 // start a gtk_window_begin_move_drag on the second GDK_BUTTON_PRESS, | |
| 2003 // the call to gtk_window_maximize fails. To work around this, we | |
| 2004 // keep track of the last click and if it's going to be a double click, | |
| 2005 // we don't call gtk_window_begin_move_drag. | |
| 2006 static GtkSettings* settings = gtk_settings_get_default(); | |
| 2007 gint double_click_time = 250; | |
| 2008 gint double_click_distance = 5; | |
| 2009 g_object_get(G_OBJECT(settings), | |
| 2010 "gtk-double-click-time", &double_click_time, | |
| 2011 "gtk-double-click-distance", &double_click_distance, | |
| 2012 NULL); | |
| 2013 | |
| 2014 guint32 click_time = event->time - last_click_time; | |
| 2015 int click_move_x = abs(event->x - last_click_position.x()); | |
| 2016 int click_move_y = abs(event->y - last_click_position.y()); | |
| 2017 | |
| 2018 if (click_time > static_cast<guint32>(double_click_time) || | |
| 2019 click_move_x > double_click_distance || | |
| 2020 click_move_y > double_click_distance) { | |
| 2021 // Ignore drag requests if the window is the size of the screen. | |
| 2022 // We do this to avoid triggering fullscreen mode in metacity | |
| 2023 // (without the --no-force-fullscreen flag) and in compiz (with | |
| 2024 // Legacy Fullscreen Mode enabled). | |
| 2025 if (!BoundsMatchMonitorSize()) { | |
| 2026 gtk_window_begin_move_drag(window_, event->button, | |
| 2027 static_cast<gint>(event->x_root), | |
| 2028 static_cast<gint>(event->y_root), | |
| 2029 event->time); | |
| 2030 } | |
| 2031 return TRUE; | |
| 2032 } | |
| 2033 } else if (has_hit_edge) { | |
| 2034 gtk_window_begin_resize_drag(window_, edge, event->button, | |
| 2035 static_cast<gint>(event->x_root), | |
| 2036 static_cast<gint>(event->y_root), | |
| 2037 event->time); | |
| 2038 return TRUE; | |
| 2039 } | |
| 2040 } else if (GDK_2BUTTON_PRESS == event->type) { | |
| 2041 if (has_hit_titlebar) { | |
| 2042 // Maximize/restore on double click. | |
| 2043 if (IsMaximized()) { | |
| 2044 UnMaximize(); | |
| 2045 } else { | |
| 2046 gtk_window_maximize(window_); | |
| 2047 } | |
| 2048 return TRUE; | |
| 2049 } | |
| 2050 } | |
| 2051 } else if (event->button == 2) { | |
| 2052 if (has_hit_titlebar || has_hit_edge) { | |
| 2053 gdk_window_lower(GTK_WIDGET(window_)->window); | |
| 2054 } | |
| 2055 return TRUE; | |
| 2056 } else if (event->button == 3) { | |
| 2057 if (has_hit_titlebar) { | |
| 2058 titlebar_->ShowContextMenu(); | |
| 2059 return TRUE; | |
| 2060 } | |
| 2061 } | |
| 2062 | |
| 2063 return FALSE; // Continue to propagate the event. | |
| 2064 } | |
| 2065 | |
| 2066 // static | |
| 2067 void BrowserWindowGtk::MainWindowMapped(GtkWidget* widget) { | |
| 2068 // Map the X Window ID of the window to our window. | |
| 2069 XID xid = x11_util::GetX11WindowFromGtkWidget(widget); | |
| 2070 BrowserWindowGtk::xid_map_.insert( | |
| 2071 std::pair<XID, GtkWindow*>(xid, GTK_WINDOW(widget))); | |
| 2072 } | |
| 2073 | |
| 2074 // static | |
| 2075 void BrowserWindowGtk::MainWindowUnMapped(GtkWidget* widget) { | |
| 2076 // Unmap the X Window ID. | |
| 2077 XID xid = x11_util::GetX11WindowFromGtkWidget(widget); | |
| 2078 BrowserWindowGtk::xid_map_.erase(xid); | |
| 2079 } | |
| 2080 | |
| 2081 gboolean BrowserWindowGtk::OnFocusIn(GtkWidget* widget, | |
| 2082 GdkEventFocus* event) { | |
| 2083 BrowserList::SetLastActive(browser_.get()); | |
| 2084 return FALSE; | |
| 2085 } | |
| 2086 | |
| 2087 gboolean BrowserWindowGtk::OnFocusOut(GtkWidget* widget, | |
| 2088 GdkEventFocus* event) { | |
| 2089 return FALSE; | |
| 2090 } | |
| 2091 | |
| 2092 void BrowserWindowGtk::ShowSupportedWindowFeatures() { | |
| 2093 if (IsTabStripSupported()) | |
| 2094 tabstrip_->Show(); | |
| 2095 | |
| 2096 if (IsToolbarSupported()) { | |
| 2097 toolbar_->Show(); | |
| 2098 gtk_widget_show(toolbar_border_); | |
| 2099 gdk_window_lower(toolbar_border_->window); | |
| 2100 } | |
| 2101 | |
| 2102 if (IsBookmarkBarSupported()) | |
| 2103 MaybeShowBookmarkBar(false); | |
| 2104 } | |
| 2105 | |
| 2106 void BrowserWindowGtk::HideUnsupportedWindowFeatures() { | |
| 2107 if (!IsTabStripSupported()) | |
| 2108 tabstrip_->Hide(); | |
| 2109 | |
| 2110 if (!IsToolbarSupported()) | |
| 2111 toolbar_->Hide(); | |
| 2112 | |
| 2113 // If the bookmark bar shelf is unsupported, then we never create it. | |
| 2114 } | |
| 2115 | |
| 2116 bool BrowserWindowGtk::IsTabStripSupported() const { | |
| 2117 return browser_->SupportsWindowFeature(Browser::FEATURE_TABSTRIP); | |
| 2118 } | |
| 2119 | |
| 2120 bool BrowserWindowGtk::IsToolbarSupported() const { | |
| 2121 return browser_->SupportsWindowFeature(Browser::FEATURE_TOOLBAR) || | |
| 2122 browser_->SupportsWindowFeature(Browser::FEATURE_LOCATIONBAR); | |
| 2123 } | |
| 2124 | |
| 2125 bool BrowserWindowGtk::IsBookmarkBarSupported() const { | |
| 2126 return browser_->SupportsWindowFeature(Browser::FEATURE_BOOKMARKBAR); | |
| 2127 } | |
| 2128 | |
| 2129 bool BrowserWindowGtk::UsingCustomPopupFrame() const { | |
| 2130 GtkThemeProvider* theme_provider = GtkThemeProvider::GetFrom( | |
| 2131 browser()->profile()); | |
| 2132 return !theme_provider->UseGtkTheme() && | |
| 2133 browser()->type() & Browser::TYPE_POPUP; | |
| 2134 } | |
| 2135 | |
| 2136 bool BrowserWindowGtk::GetWindowEdge(int x, int y, GdkWindowEdge* edge) { | |
| 2137 if (!UseCustomFrame()) | |
| 2138 return false; | |
| 2139 | |
| 2140 if (IsMaximized() || IsFullscreen()) | |
| 2141 return false; | |
| 2142 | |
| 2143 if (x < kFrameBorderThickness) { | |
| 2144 // Left edge. | |
| 2145 if (y < kResizeAreaCornerSize - kTopResizeAdjust) { | |
| 2146 *edge = GDK_WINDOW_EDGE_NORTH_WEST; | |
| 2147 } else if (y < bounds_.height() - kResizeAreaCornerSize) { | |
| 2148 *edge = GDK_WINDOW_EDGE_WEST; | |
| 2149 } else { | |
| 2150 *edge = GDK_WINDOW_EDGE_SOUTH_WEST; | |
| 2151 } | |
| 2152 return true; | |
| 2153 } else if (x < bounds_.width() - kFrameBorderThickness) { | |
| 2154 if (y < kFrameBorderThickness - kTopResizeAdjust) { | |
| 2155 // Top edge. | |
| 2156 if (x < kResizeAreaCornerSize) { | |
| 2157 *edge = GDK_WINDOW_EDGE_NORTH_WEST; | |
| 2158 } else if (x < bounds_.width() - kResizeAreaCornerSize) { | |
| 2159 *edge = GDK_WINDOW_EDGE_NORTH; | |
| 2160 } else { | |
| 2161 *edge = GDK_WINDOW_EDGE_NORTH_EAST; | |
| 2162 } | |
| 2163 } else if (y < bounds_.height() - kFrameBorderThickness) { | |
| 2164 // Ignore the middle content area. | |
| 2165 return false; | |
| 2166 } else { | |
| 2167 // Bottom edge. | |
| 2168 if (x < kResizeAreaCornerSize) { | |
| 2169 *edge = GDK_WINDOW_EDGE_SOUTH_WEST; | |
| 2170 } else if (x < bounds_.width() - kResizeAreaCornerSize) { | |
| 2171 *edge = GDK_WINDOW_EDGE_SOUTH; | |
| 2172 } else { | |
| 2173 *edge = GDK_WINDOW_EDGE_SOUTH_EAST; | |
| 2174 } | |
| 2175 } | |
| 2176 return true; | |
| 2177 } else { | |
| 2178 // Right edge. | |
| 2179 if (y < kResizeAreaCornerSize - kTopResizeAdjust) { | |
| 2180 *edge = GDK_WINDOW_EDGE_NORTH_EAST; | |
| 2181 } else if (y < bounds_.height() - kResizeAreaCornerSize) { | |
| 2182 *edge = GDK_WINDOW_EDGE_EAST; | |
| 2183 } else { | |
| 2184 *edge = GDK_WINDOW_EDGE_SOUTH_EAST; | |
| 2185 } | |
| 2186 return true; | |
| 2187 } | |
| 2188 | |
| 2189 NOTREACHED(); | |
| 2190 return false; | |
| 2191 } | |
| 2192 | |
| 2193 bool BrowserWindowGtk::UseCustomFrame() { | |
| 2194 // We don't use the custom frame for app mode windows or app window popups. | |
| 2195 return use_custom_frame_pref_.GetValue() && | |
| 2196 browser_->type() != Browser::TYPE_APP && | |
| 2197 browser_->type() != Browser::TYPE_APP_POPUP; | |
| 2198 } | |
| 2199 | |
| 2200 bool BrowserWindowGtk::BoundsMatchMonitorSize() { | |
| 2201 // A screen can be composed of multiple monitors. | |
| 2202 GdkScreen* screen = gtk_window_get_screen(window_); | |
| 2203 gint monitor_num = gdk_screen_get_monitor_at_window(screen, | |
| 2204 GTK_WIDGET(window_)->window); | |
| 2205 | |
| 2206 GdkRectangle monitor_size; | |
| 2207 gdk_screen_get_monitor_geometry(screen, monitor_num, &monitor_size); | |
| 2208 return bounds_.size() == gfx::Size(monitor_size.width, monitor_size.height); | |
| 2209 } | |
| 2210 | |
| 2211 void BrowserWindowGtk::PlaceBookmarkBar(bool is_floating) { | |
| 2212 GtkWidget* parent = gtk_widget_get_parent(bookmark_bar_->widget()); | |
| 2213 if (parent) | |
| 2214 gtk_container_remove(GTK_CONTAINER(parent), bookmark_bar_->widget()); | |
| 2215 | |
| 2216 if (!is_floating) { | |
| 2217 // Place the bookmark bar at the end of |window_vbox_|; this happens after | |
| 2218 // we have placed the render area at the end of |window_vbox_| so we will | |
| 2219 // be above the render area. | |
| 2220 gtk_box_pack_end(GTK_BOX(window_vbox_), bookmark_bar_->widget(), | |
| 2221 FALSE, FALSE, 0); | |
| 2222 } else { | |
| 2223 // Place the bookmark bar at the end of the render area; this happens after | |
| 2224 // the tab contents container has been placed there so we will be | |
| 2225 // above the webpage (in terms of y). | |
| 2226 gtk_box_pack_end(GTK_BOX(render_area_vbox_), bookmark_bar_->widget(), | |
| 2227 FALSE, FALSE, 0); | |
| 2228 } | |
| 2229 } | |
| 2230 | |
| 2231 // static | |
| 2232 bool BrowserWindowGtk::GetCustomFramePrefDefault() { | |
| 2233 std::string wm_name; | |
| 2234 if (!x11_util::GetWindowManagerName(&wm_name)) | |
| 2235 return false; | |
| 2236 | |
| 2237 // Ideally, we'd use the custom frame by default and just fall back on using | |
| 2238 // system decorations for the few (?) tiling window managers where the custom | |
| 2239 // frame doesn't make sense (e.g. awesome, ion3, ratpoison, xmonad, etc.) or | |
| 2240 // other WMs where it has issues (e.g. Fluxbox -- see issue 19130). The EWMH | |
| 2241 // _NET_SUPPORTING_WM property makes it easy to look up a name for the current | |
| 2242 // WM, but at least some of the WMs in the latter group don't set it. | |
| 2243 // Instead, we default to using system decorations for all WMs and | |
| 2244 // special-case the ones where the custom frame should be used. These names | |
| 2245 // are taken from the WMs' source code. | |
| 2246 return (wm_name == "Blackbox" || | |
| 2247 wm_name == "compiz" || | |
| 2248 wm_name == "e16" || // Enlightenment DR16 | |
| 2249 wm_name == "Metacity" || | |
| 2250 wm_name == "Mutter" || | |
| 2251 wm_name == "Openbox" || | |
| 2252 wm_name == "Xfwm4"); | |
| 2253 } | |
| OLD | NEW |