| OLD | NEW |
| (Empty) |
| 1 // Copyright 2013 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/ui/gtk/apps/native_app_window_gtk.h" | |
| 6 | |
| 7 #include <gdk/gdkx.h> | |
| 8 #include <vector> | |
| 9 | |
| 10 #include "base/message_loop/message_pump_gtk.h" | |
| 11 #include "base/strings/utf_string_conversions.h" | |
| 12 #include "chrome/browser/profiles/profile.h" | |
| 13 #include "chrome/browser/ui/gtk/extensions/extension_keybinding_registry_gtk.h" | |
| 14 #include "chrome/browser/ui/gtk/gtk_util.h" | |
| 15 #include "chrome/browser/ui/gtk/gtk_window_util.h" | |
| 16 #include "chrome/browser/web_applications/web_app.h" | |
| 17 #include "content/public/browser/render_view_host.h" | |
| 18 #include "content/public/browser/render_widget_host_view.h" | |
| 19 #include "content/public/browser/web_contents.h" | |
| 20 #include "content/public/browser/web_contents_view.h" | |
| 21 #include "extensions/common/extension.h" | |
| 22 #include "ui/base/x/active_window_watcher_x.h" | |
| 23 #include "ui/gfx/gtk_util.h" | |
| 24 #include "ui/gfx/image/image.h" | |
| 25 #include "ui/gfx/rect.h" | |
| 26 | |
| 27 using apps::AppWindow; | |
| 28 | |
| 29 namespace { | |
| 30 | |
| 31 // The timeout in milliseconds before we'll get the true window position with | |
| 32 // gtk_window_get_position() after the last GTK configure-event signal. | |
| 33 const int kDebounceTimeoutMilliseconds = 100; | |
| 34 | |
| 35 const char* kAtomsToCache[] = { | |
| 36 "_NET_WM_STATE", | |
| 37 "_NET_WM_STATE_HIDDEN", | |
| 38 NULL | |
| 39 }; | |
| 40 | |
| 41 } // namespace | |
| 42 | |
| 43 NativeAppWindowGtk::NativeAppWindowGtk(AppWindow* app_window, | |
| 44 const AppWindow::CreateParams& params) | |
| 45 : app_window_(app_window), | |
| 46 window_(NULL), | |
| 47 state_(GDK_WINDOW_STATE_WITHDRAWN), | |
| 48 is_active_(false), | |
| 49 content_thinks_its_fullscreen_(false), | |
| 50 maximize_pending_(false), | |
| 51 frameless_(params.frame == AppWindow::FRAME_NONE), | |
| 52 always_on_top_(params.always_on_top), | |
| 53 frame_cursor_(NULL), | |
| 54 atom_cache_(base::MessagePumpGtk::GetDefaultXDisplay(), kAtomsToCache), | |
| 55 is_x_event_listened_(false) { | |
| 56 Observe(web_contents()); | |
| 57 | |
| 58 window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); | |
| 59 | |
| 60 gfx::NativeView native_view = | |
| 61 web_contents()->GetView()->GetNativeView(); | |
| 62 gtk_container_add(GTK_CONTAINER(window_), native_view); | |
| 63 | |
| 64 gfx::Insets frame_insets = GetFrameInsets(); | |
| 65 gfx::Rect initial_bounds = params.GetInitialWindowBounds(frame_insets); | |
| 66 | |
| 67 typedef apps::AppWindow::BoundsSpecification BoundsSpecification; | |
| 68 if (initial_bounds.x() != BoundsSpecification::kUnspecifiedPosition && | |
| 69 initial_bounds.y() != BoundsSpecification::kUnspecifiedPosition) { | |
| 70 gtk_window_move(window_, initial_bounds.x(), initial_bounds.y()); | |
| 71 } | |
| 72 | |
| 73 // This is done to avoid a WM "feature" where setting the window size to | |
| 74 // the monitor size causes the WM to set the EWMH for full screen mode. | |
| 75 int win_height = initial_bounds.height(); | |
| 76 if (frameless_ && | |
| 77 gtk_window_util::BoundsMatchMonitorSize(window_, initial_bounds)) { | |
| 78 win_height -= 1; | |
| 79 } | |
| 80 gtk_window_set_default_size(window_, initial_bounds.width(), win_height); | |
| 81 | |
| 82 resizable_ = params.resizable; | |
| 83 if (!resizable_) { | |
| 84 // If the window doesn't have a size request when we set resizable to | |
| 85 // false, GTK will shrink the window to 1x1px. | |
| 86 gtk_widget_set_size_request(GTK_WIDGET(window_), | |
| 87 initial_bounds.width(), win_height); | |
| 88 gtk_window_set_resizable(window_, FALSE); | |
| 89 } | |
| 90 | |
| 91 // make sure bounds_ and restored_bounds_ have correct values until we | |
| 92 // get our first configure-event | |
| 93 bounds_ = restored_bounds_ = initial_bounds; | |
| 94 gint x, y; | |
| 95 gtk_window_get_position(window_, &x, &y); | |
| 96 bounds_.set_origin(gfx::Point(x, y)); | |
| 97 | |
| 98 // Hide titlebar when {frame: 'none'} specified on AppWindow.. | |
| 99 if (frameless_) | |
| 100 gtk_window_set_decorated(window_, false); | |
| 101 | |
| 102 if (always_on_top_) | |
| 103 gtk_window_set_keep_above(window_, TRUE); | |
| 104 | |
| 105 SetContentSizeConstraints(params.GetContentMinimumSize(frame_insets), | |
| 106 params.GetContentMaximumSize(frame_insets)); | |
| 107 | |
| 108 // In some (older) versions of compiz, raising top-level windows when they | |
| 109 // are partially off-screen causes them to get snapped back on screen, not | |
| 110 // always even on the current virtual desktop. If we are running under | |
| 111 // compiz, suppress such raises, as they are not necessary in compiz anyway. | |
| 112 if (ui::GuessWindowManager() == ui::WM_COMPIZ) | |
| 113 suppress_window_raise_ = true; | |
| 114 | |
| 115 gtk_window_set_title(window_, extension()->name().c_str()); | |
| 116 | |
| 117 std::string app_name = web_app::GenerateApplicationNameFromExtensionId( | |
| 118 extension()->id()); | |
| 119 gtk_window_util::SetWindowCustomClass(window_, | |
| 120 web_app::GetWMClassFromAppName(app_name)); | |
| 121 | |
| 122 g_signal_connect(window_, "delete-event", | |
| 123 G_CALLBACK(OnMainWindowDeleteEventThunk), this); | |
| 124 g_signal_connect(window_, "configure-event", | |
| 125 G_CALLBACK(OnConfigureThunk), this); | |
| 126 g_signal_connect(window_, "window-state-event", | |
| 127 G_CALLBACK(OnWindowStateThunk), this); | |
| 128 if (frameless_) { | |
| 129 g_signal_connect(window_, "button-press-event", | |
| 130 G_CALLBACK(OnButtonPressThunk), this); | |
| 131 g_signal_connect(window_, "motion-notify-event", | |
| 132 G_CALLBACK(OnMouseMoveEventThunk), this); | |
| 133 } | |
| 134 | |
| 135 // If _NET_WM_STATE_HIDDEN is in _NET_SUPPORTED, listen for XEvent to work | |
| 136 // around GTK+ not reporting minimization state changes. See comment in the | |
| 137 // |OnXEvent|. | |
| 138 std::vector< ::Atom> supported_atoms; | |
| 139 if (ui::GetAtomArrayProperty(ui::GetX11RootWindow(), | |
| 140 "_NET_SUPPORTED", | |
| 141 &supported_atoms)) { | |
| 142 if (std::find(supported_atoms.begin(), | |
| 143 supported_atoms.end(), | |
| 144 atom_cache_.GetAtom("_NET_WM_STATE_HIDDEN")) != | |
| 145 supported_atoms.end()) { | |
| 146 GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(window_)); | |
| 147 gdk_window_add_filter(window, | |
| 148 &NativeAppWindowGtk::OnXEventThunk, | |
| 149 this); | |
| 150 is_x_event_listened_ = true; | |
| 151 } | |
| 152 } | |
| 153 | |
| 154 // Add the keybinding registry. | |
| 155 extension_keybinding_registry_.reset(new ExtensionKeybindingRegistryGtk( | |
| 156 Profile::FromBrowserContext(app_window_->browser_context()), | |
| 157 window_, | |
| 158 extensions::ExtensionKeybindingRegistry::PLATFORM_APPS_ONLY, | |
| 159 NULL)); | |
| 160 | |
| 161 ui::ActiveWindowWatcherX::AddObserver(this); | |
| 162 } | |
| 163 | |
| 164 NativeAppWindowGtk::~NativeAppWindowGtk() { | |
| 165 ui::ActiveWindowWatcherX::RemoveObserver(this); | |
| 166 if (is_x_event_listened_) { | |
| 167 gdk_window_remove_filter(NULL, | |
| 168 &NativeAppWindowGtk::OnXEventThunk, | |
| 169 this); | |
| 170 } | |
| 171 } | |
| 172 | |
| 173 bool NativeAppWindowGtk::IsActive() const { | |
| 174 if (ui::ActiveWindowWatcherX::WMSupportsActivation()) | |
| 175 return is_active_; | |
| 176 | |
| 177 // This still works even though we don't get the activation notification. | |
| 178 return gtk_window_is_active(window_); | |
| 179 } | |
| 180 | |
| 181 bool NativeAppWindowGtk::IsMaximized() const { | |
| 182 return (state_ & GDK_WINDOW_STATE_MAXIMIZED); | |
| 183 } | |
| 184 | |
| 185 bool NativeAppWindowGtk::IsMinimized() const { | |
| 186 return (state_ & GDK_WINDOW_STATE_ICONIFIED); | |
| 187 } | |
| 188 | |
| 189 bool NativeAppWindowGtk::IsFullscreen() const { | |
| 190 return (state_ & GDK_WINDOW_STATE_FULLSCREEN); | |
| 191 } | |
| 192 | |
| 193 gfx::NativeWindow NativeAppWindowGtk::GetNativeWindow() { | |
| 194 return window_; | |
| 195 } | |
| 196 | |
| 197 gfx::Rect NativeAppWindowGtk::GetRestoredBounds() const { | |
| 198 gfx::Rect window_bounds = restored_bounds_; | |
| 199 window_bounds.Inset(-GetFrameInsets()); | |
| 200 return window_bounds; | |
| 201 } | |
| 202 | |
| 203 ui::WindowShowState NativeAppWindowGtk::GetRestoredState() const { | |
| 204 if (IsMaximized()) | |
| 205 return ui::SHOW_STATE_MAXIMIZED; | |
| 206 if (IsFullscreen()) | |
| 207 return ui::SHOW_STATE_FULLSCREEN; | |
| 208 return ui::SHOW_STATE_NORMAL; | |
| 209 } | |
| 210 | |
| 211 gfx::Rect NativeAppWindowGtk::GetBounds() const { | |
| 212 // :GetBounds() is expecting the outer window bounds to be returned (ie. | |
| 213 // including window decorations). The internal |bounds_| is not including them | |
| 214 // and trying to add the decoration to |bounds_| would require calling | |
| 215 // gdk_window_get_frame_extents. The best thing to do is to directly get the | |
| 216 // frame bounds and only use the internal value if we can't. | |
| 217 GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); | |
| 218 if (!gdk_window) | |
| 219 return bounds_; | |
| 220 | |
| 221 GdkRectangle window_bounds = {0}; | |
| 222 gdk_window_get_frame_extents(gdk_window, &window_bounds); | |
| 223 return gfx::Rect(window_bounds.x, window_bounds.y, | |
| 224 window_bounds.width, window_bounds.height); | |
| 225 } | |
| 226 | |
| 227 void NativeAppWindowGtk::Show() { | |
| 228 gtk_window_present(window_); | |
| 229 } | |
| 230 | |
| 231 void NativeAppWindowGtk::ShowInactive() { | |
| 232 gtk_window_set_focus_on_map(window_, false); | |
| 233 gtk_widget_show(GTK_WIDGET(window_)); | |
| 234 } | |
| 235 | |
| 236 void NativeAppWindowGtk::Hide() { | |
| 237 gtk_widget_hide(GTK_WIDGET(window_)); | |
| 238 } | |
| 239 | |
| 240 void NativeAppWindowGtk::Close() { | |
| 241 app_window_->OnNativeWindowChanged(); | |
| 242 | |
| 243 // Cancel any pending callback from the window configure debounce timer. | |
| 244 window_configure_debounce_timer_.Stop(); | |
| 245 | |
| 246 GtkWidget* window = GTK_WIDGET(window_); | |
| 247 // To help catch bugs in any event handlers that might get fired during the | |
| 248 // destruction, set window_ to NULL before any handlers will run. | |
| 249 window_ = NULL; | |
| 250 | |
| 251 // OnNativeClose does a delete this so no other members should | |
| 252 // be accessed after. gtk_widget_destroy is safe (and must | |
| 253 // be last). | |
| 254 app_window_->OnNativeClose(); | |
| 255 gtk_widget_destroy(window); | |
| 256 } | |
| 257 | |
| 258 void NativeAppWindowGtk::Activate() { | |
| 259 gtk_window_present(window_); | |
| 260 } | |
| 261 | |
| 262 void NativeAppWindowGtk::Deactivate() { | |
| 263 gdk_window_lower(gtk_widget_get_window(GTK_WIDGET(window_))); | |
| 264 } | |
| 265 | |
| 266 void NativeAppWindowGtk::Maximize() { | |
| 267 // Represent the window first in order to keep the maximization behavior | |
| 268 // consistency with Windows platform. Otherwise the window will be hidden if | |
| 269 // it has been minimized. | |
| 270 gtk_window_present(window_); | |
| 271 | |
| 272 if (!resizable_) { | |
| 273 // When the window is not resizable, we still want to make this call succeed | |
| 274 // but gtk will not allow it if the window is not resizable. The actual | |
| 275 // maximization will happen with the subsequent OnConfigureDebounced call, | |
| 276 // that will be triggered when the window manager's resizable property | |
| 277 // changes. | |
| 278 maximize_pending_ = true; | |
| 279 gtk_window_set_resizable(window_, TRUE); | |
| 280 } else { | |
| 281 gtk_window_maximize(window_); | |
| 282 } | |
| 283 } | |
| 284 | |
| 285 void NativeAppWindowGtk::Minimize() { | |
| 286 gtk_window_iconify(window_); | |
| 287 } | |
| 288 | |
| 289 void NativeAppWindowGtk::Restore() { | |
| 290 if (IsMaximized()) | |
| 291 gtk_window_unmaximize(window_); | |
| 292 else if (IsMinimized()) | |
| 293 gtk_window_deiconify(window_); | |
| 294 | |
| 295 // Represent the window to keep restoration behavior consistency with Windows | |
| 296 // platform. | |
| 297 // TODO(zhchbin): verify whether we need this until http://crbug.com/261013 is | |
| 298 // fixed. | |
| 299 gtk_window_present(window_); | |
| 300 } | |
| 301 | |
| 302 void NativeAppWindowGtk::SetBounds(const gfx::Rect& bounds) { | |
| 303 gfx::Rect content_bounds = bounds; | |
| 304 gtk_window_move(window_, content_bounds.x(), content_bounds.y()); | |
| 305 if (!resizable_) { | |
| 306 if (frameless_ && | |
| 307 gtk_window_util::BoundsMatchMonitorSize(window_, content_bounds)) { | |
| 308 content_bounds.set_height(content_bounds.height() - 1); | |
| 309 } | |
| 310 // TODO(jeremya): set_size_request doesn't honor min/max size, so the | |
| 311 // bounds should be constrained manually. | |
| 312 gtk_widget_set_size_request(GTK_WIDGET(window_), | |
| 313 content_bounds.width(), content_bounds.height()); | |
| 314 } else { | |
| 315 gtk_window_util::SetWindowSize(window_, | |
| 316 gfx::Size(bounds.width(), bounds.height())); | |
| 317 } | |
| 318 } | |
| 319 | |
| 320 GdkFilterReturn NativeAppWindowGtk::OnXEvent(GdkXEvent* gdk_x_event, | |
| 321 GdkEvent* gdk_event) { | |
| 322 // Work around GTK+ not reporting minimization state changes. Listen | |
| 323 // for _NET_WM_STATE property changes and use _NET_WM_STATE_HIDDEN's | |
| 324 // presence to set or clear the iconified bit if _NET_WM_STATE_HIDDEN | |
| 325 // is supported. http://crbug.com/162794. | |
| 326 XEvent* x_event = static_cast<XEvent*>(gdk_x_event); | |
| 327 std::vector< ::Atom> atom_list; | |
| 328 | |
| 329 if (x_event->type == PropertyNotify && | |
| 330 x_event->xproperty.atom == atom_cache_.GetAtom("_NET_WM_STATE") && | |
| 331 GTK_WIDGET(window_)->window && | |
| 332 ui::GetAtomArrayProperty(GDK_WINDOW_XWINDOW(GTK_WIDGET(window_)->window), | |
| 333 "_NET_WM_STATE", | |
| 334 &atom_list)) { | |
| 335 std::vector< ::Atom>::iterator it = | |
| 336 std::find(atom_list.begin(), | |
| 337 atom_list.end(), | |
| 338 atom_cache_.GetAtom("_NET_WM_STATE_HIDDEN")); | |
| 339 | |
| 340 GdkWindowState previous_state = state_; | |
| 341 state_ = (it != atom_list.end()) ? GDK_WINDOW_STATE_ICONIFIED : | |
| 342 static_cast<GdkWindowState>(state_ & ~GDK_WINDOW_STATE_ICONIFIED); | |
| 343 | |
| 344 if (previous_state != state_) { | |
| 345 app_window_->OnNativeWindowChanged(); | |
| 346 } | |
| 347 } | |
| 348 | |
| 349 return GDK_FILTER_CONTINUE; | |
| 350 } | |
| 351 | |
| 352 void NativeAppWindowGtk::FlashFrame(bool flash) { | |
| 353 gtk_window_set_urgency_hint(window_, flash); | |
| 354 } | |
| 355 | |
| 356 bool NativeAppWindowGtk::IsAlwaysOnTop() const { | |
| 357 return always_on_top_; | |
| 358 } | |
| 359 | |
| 360 void NativeAppWindowGtk::RenderViewHostChanged( | |
| 361 content::RenderViewHost* old_host, | |
| 362 content::RenderViewHost* new_host) { | |
| 363 web_contents()->GetView()->Focus(); | |
| 364 } | |
| 365 | |
| 366 void NativeAppWindowGtk::SetAlwaysOnTop(bool always_on_top) { | |
| 367 if (always_on_top_ != always_on_top) { | |
| 368 // gdk_window_get_state() does not give us the correct value for the | |
| 369 // GDK_WINDOW_STATE_ABOVE bit. Cache the current state. | |
| 370 always_on_top_ = always_on_top; | |
| 371 gtk_window_set_keep_above(window_, always_on_top_ ? TRUE : FALSE); | |
| 372 } | |
| 373 } | |
| 374 | |
| 375 gfx::NativeView NativeAppWindowGtk::GetHostView() const { | |
| 376 NOTIMPLEMENTED(); | |
| 377 return NULL; | |
| 378 } | |
| 379 | |
| 380 gfx::Point NativeAppWindowGtk::GetDialogPosition(const gfx::Size& size) { | |
| 381 gint current_width = 0; | |
| 382 gint current_height = 0; | |
| 383 gtk_window_get_size(window_, ¤t_width, ¤t_height); | |
| 384 return gfx::Point(current_width / 2 - size.width() / 2, | |
| 385 current_height / 2 - size.height() / 2); | |
| 386 } | |
| 387 | |
| 388 gfx::Size NativeAppWindowGtk::GetMaximumDialogSize() { | |
| 389 gint current_width = 0; | |
| 390 gint current_height = 0; | |
| 391 gtk_window_get_size(window_, ¤t_width, ¤t_height); | |
| 392 return gfx::Size(current_width, current_height); | |
| 393 } | |
| 394 | |
| 395 void NativeAppWindowGtk::AddObserver( | |
| 396 web_modal::ModalDialogHostObserver* observer) { | |
| 397 observer_list_.AddObserver(observer); | |
| 398 } | |
| 399 | |
| 400 void NativeAppWindowGtk::RemoveObserver( | |
| 401 web_modal::ModalDialogHostObserver* observer) { | |
| 402 observer_list_.RemoveObserver(observer); | |
| 403 } | |
| 404 | |
| 405 void NativeAppWindowGtk::ActiveWindowChanged(GdkWindow* active_window) { | |
| 406 // Do nothing if we're in the process of closing the browser window. | |
| 407 if (!window_) | |
| 408 return; | |
| 409 | |
| 410 is_active_ = gtk_widget_get_window(GTK_WIDGET(window_)) == active_window; | |
| 411 if (is_active_) | |
| 412 app_window_->OnNativeWindowActivated(); | |
| 413 } | |
| 414 | |
| 415 // Callback for the delete event. This event is fired when the user tries to | |
| 416 // close the window (e.g., clicking on the X in the window manager title bar). | |
| 417 gboolean NativeAppWindowGtk::OnMainWindowDeleteEvent(GtkWidget* widget, | |
| 418 GdkEvent* event) { | |
| 419 Close(); | |
| 420 | |
| 421 // Return true to prevent the GTK window from being destroyed. Close will | |
| 422 // destroy it for us. | |
| 423 return TRUE; | |
| 424 } | |
| 425 | |
| 426 gboolean NativeAppWindowGtk::OnConfigure(GtkWidget* widget, | |
| 427 GdkEventConfigure* event) { | |
| 428 // We update |bounds_| but not |restored_bounds_| here. The latter needs | |
| 429 // to be updated conditionally when the window is non-maximized and non- | |
| 430 // fullscreen, but whether those state updates have been processed yet is | |
| 431 // window-manager specific. We update |restored_bounds_| in the debounced | |
| 432 // handler below, after the window state has been updated. | |
| 433 bounds_.SetRect(event->x, event->y, event->width, event->height); | |
| 434 | |
| 435 // The GdkEventConfigure* we get here doesn't have quite the right | |
| 436 // coordinates though (they're relative to the drawable window area, rather | |
| 437 // than any window manager decorations, if enabled), so we need to call | |
| 438 // gtk_window_get_position() to get the right values. (Otherwise session | |
| 439 // restore, if enabled, will restore windows to incorrect positions.) That's | |
| 440 // a round trip to the X server though, so we set a debounce timer and only | |
| 441 // call it (in OnConfigureDebounced() below) after we haven't seen a | |
| 442 // reconfigure event in a short while. | |
| 443 // We don't use Reset() because the timer may not yet be running. | |
| 444 // (In that case Stop() is a no-op.) | |
| 445 window_configure_debounce_timer_.Stop(); | |
| 446 window_configure_debounce_timer_.Start(FROM_HERE, | |
| 447 base::TimeDelta::FromMilliseconds(kDebounceTimeoutMilliseconds), this, | |
| 448 &NativeAppWindowGtk::OnConfigureDebounced); | |
| 449 | |
| 450 return FALSE; | |
| 451 } | |
| 452 | |
| 453 void NativeAppWindowGtk::OnConfigureDebounced() { | |
| 454 gtk_window_util::UpdateWindowPosition(this, &bounds_, &restored_bounds_); | |
| 455 app_window_->OnNativeWindowChanged(); | |
| 456 | |
| 457 FOR_EACH_OBSERVER(web_modal::ModalDialogHostObserver, | |
| 458 observer_list_, | |
| 459 OnPositionRequiresUpdate()); | |
| 460 | |
| 461 // Fullscreen of non-resizable windows requires them to be made resizable | |
| 462 // first. After that takes effect and OnConfigure is called we transition | |
| 463 // to fullscreen. | |
| 464 if (!IsFullscreen() && IsFullscreenOrPending()) { | |
| 465 gtk_window_fullscreen(window_); | |
| 466 } | |
| 467 | |
| 468 // maximize_pending_ is the boolean that lets us know that the window is in | |
| 469 // the process of being maximized but was set as not resizable. | |
| 470 // This function will be called twice during the maximization process: | |
| 471 // 1. gtk_window_maximize() is called to maximize the window; | |
| 472 // 2. gtk_set_resizable(, FALSE) is called to make the window no longer | |
| 473 // resizable. | |
| 474 // gtk_window_maximize() will cause ::OnConfigureDebounced to be called | |
| 475 // again, at which time we will run into the second step. | |
| 476 if (maximize_pending_) { | |
| 477 if (!(state_ & GDK_WINDOW_STATE_MAXIMIZED)) { | |
| 478 gtk_window_maximize(window_); | |
| 479 } else { | |
| 480 maximize_pending_ = false; | |
| 481 if (!resizable_) | |
| 482 gtk_window_set_resizable(window_, FALSE); | |
| 483 } | |
| 484 } | |
| 485 } | |
| 486 | |
| 487 void NativeAppWindowGtk::UpdateContentMinMaxSize() { | |
| 488 GdkGeometry hints; | |
| 489 int hints_mask = GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE; | |
| 490 | |
| 491 gfx::Size min_size = size_constraints_.GetMinimumSize(); | |
| 492 hints.min_height = min_size.height(); | |
| 493 hints.min_width = min_size.width(); | |
| 494 | |
| 495 gfx::Size max_size = size_constraints_.GetMaximumSize(); | |
| 496 const int kUnboundedSize = apps::SizeConstraints::kUnboundedSize; | |
| 497 hints.max_height = max_size.height() == kUnboundedSize ? | |
| 498 G_MAXINT : max_size.height(); | |
| 499 hints.max_width = max_size.width() == kUnboundedSize ? | |
| 500 G_MAXINT : max_size.width(); | |
| 501 | |
| 502 gtk_window_set_geometry_hints( | |
| 503 window_, | |
| 504 GTK_WIDGET(window_), | |
| 505 &hints, | |
| 506 static_cast<GdkWindowHints>(hints_mask)); | |
| 507 } | |
| 508 | |
| 509 gboolean NativeAppWindowGtk::OnWindowState(GtkWidget* sender, | |
| 510 GdkEventWindowState* event) { | |
| 511 state_ = event->new_window_state; | |
| 512 | |
| 513 if (content_thinks_its_fullscreen_ && | |
| 514 !(state_ & GDK_WINDOW_STATE_FULLSCREEN)) { | |
| 515 content_thinks_its_fullscreen_ = false; | |
| 516 content::RenderViewHost* rvh = web_contents()->GetRenderViewHost(); | |
| 517 if (rvh) | |
| 518 rvh->ExitFullscreen(); | |
| 519 } | |
| 520 | |
| 521 return FALSE; | |
| 522 } | |
| 523 | |
| 524 bool NativeAppWindowGtk::GetWindowEdge(int x, int y, GdkWindowEdge* edge) { | |
| 525 if (!frameless_) | |
| 526 return false; | |
| 527 | |
| 528 if (IsMaximized() || IsFullscreen()) | |
| 529 return false; | |
| 530 | |
| 531 return gtk_window_util::GetWindowEdge(bounds_.size(), 0, x, y, edge); | |
| 532 } | |
| 533 | |
| 534 gboolean NativeAppWindowGtk::OnMouseMoveEvent(GtkWidget* widget, | |
| 535 GdkEventMotion* event) { | |
| 536 if (!frameless_) { | |
| 537 // Reset the cursor. | |
| 538 if (frame_cursor_) { | |
| 539 frame_cursor_ = NULL; | |
| 540 gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), NULL); | |
| 541 } | |
| 542 return FALSE; | |
| 543 } | |
| 544 | |
| 545 if (!resizable_) | |
| 546 return FALSE; | |
| 547 | |
| 548 // Update the cursor if we're on the custom frame border. | |
| 549 GdkWindowEdge edge; | |
| 550 bool has_hit_edge = GetWindowEdge(static_cast<int>(event->x), | |
| 551 static_cast<int>(event->y), &edge); | |
| 552 GdkCursorType new_cursor = GDK_LAST_CURSOR; | |
| 553 if (has_hit_edge) | |
| 554 new_cursor = gtk_window_util::GdkWindowEdgeToGdkCursorType(edge); | |
| 555 | |
| 556 GdkCursorType last_cursor = GDK_LAST_CURSOR; | |
| 557 if (frame_cursor_) | |
| 558 last_cursor = frame_cursor_->type; | |
| 559 | |
| 560 if (last_cursor != new_cursor) { | |
| 561 frame_cursor_ = has_hit_edge ? gfx::GetCursor(new_cursor) : NULL; | |
| 562 gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), | |
| 563 frame_cursor_); | |
| 564 } | |
| 565 return FALSE; | |
| 566 } | |
| 567 | |
| 568 gboolean NativeAppWindowGtk::OnButtonPress(GtkWidget* widget, | |
| 569 GdkEventButton* event) { | |
| 570 DCHECK(frameless_); | |
| 571 // Make the button press coordinate relative to the browser window. | |
| 572 int win_x, win_y; | |
| 573 GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); | |
| 574 gdk_window_get_origin(gdk_window, &win_x, &win_y); | |
| 575 | |
| 576 GdkWindowEdge edge; | |
| 577 gfx::Point point(static_cast<int>(event->x_root - win_x), | |
| 578 static_cast<int>(event->y_root - win_y)); | |
| 579 bool has_hit_edge = resizable_ && GetWindowEdge(point.x(), point.y(), &edge); | |
| 580 bool has_hit_titlebar = | |
| 581 draggable_region_ && draggable_region_->contains(event->x, event->y); | |
| 582 | |
| 583 if (event->button == 1) { | |
| 584 if (GDK_BUTTON_PRESS == event->type) { | |
| 585 // Raise the window after a click on either the titlebar or the border to | |
| 586 // match the behavior of most window managers, unless that behavior has | |
| 587 // been suppressed. | |
| 588 if ((has_hit_titlebar || has_hit_edge) && !suppress_window_raise_) | |
| 589 gdk_window_raise(GTK_WIDGET(widget)->window); | |
| 590 | |
| 591 if (has_hit_edge) { | |
| 592 gtk_window_begin_resize_drag(window_, edge, event->button, | |
| 593 static_cast<gint>(event->x_root), | |
| 594 static_cast<gint>(event->y_root), | |
| 595 event->time); | |
| 596 return TRUE; | |
| 597 } else if (has_hit_titlebar) { | |
| 598 return gtk_window_util::HandleTitleBarLeftMousePress( | |
| 599 window_, bounds_, event); | |
| 600 } | |
| 601 } else if (GDK_2BUTTON_PRESS == event->type) { | |
| 602 if (has_hit_titlebar && resizable_) { | |
| 603 // Maximize/restore on double click. | |
| 604 if (IsMaximized()) { | |
| 605 gtk_window_util::UnMaximize(GTK_WINDOW(widget), | |
| 606 bounds_, restored_bounds_); | |
| 607 } else { | |
| 608 gtk_window_maximize(window_); | |
| 609 } | |
| 610 return TRUE; | |
| 611 } | |
| 612 } | |
| 613 } else if (event->button == 2) { | |
| 614 if (has_hit_titlebar || has_hit_edge) | |
| 615 gdk_window_lower(gdk_window); | |
| 616 return TRUE; | |
| 617 } | |
| 618 | |
| 619 return FALSE; | |
| 620 } | |
| 621 | |
| 622 // NativeAppWindow implementation: | |
| 623 | |
| 624 void NativeAppWindowGtk::SetFullscreen(int fullscreen_types) { | |
| 625 bool fullscreen = (fullscreen_types != AppWindow::FULLSCREEN_TYPE_NONE); | |
| 626 content_thinks_its_fullscreen_ = fullscreen; | |
| 627 if (fullscreen) { | |
| 628 if (resizable_) { | |
| 629 gtk_window_fullscreen(window_); | |
| 630 } else { | |
| 631 // We must first make the window resizable. That won't take effect | |
| 632 // immediately, so OnConfigureDebounced completes the fullscreen call. | |
| 633 gtk_window_set_resizable(window_, TRUE); | |
| 634 } | |
| 635 } else { | |
| 636 gtk_window_unfullscreen(window_); | |
| 637 if (!resizable_) | |
| 638 gtk_window_set_resizable(window_, FALSE); | |
| 639 } | |
| 640 } | |
| 641 | |
| 642 bool NativeAppWindowGtk::IsFullscreenOrPending() const { | |
| 643 // |content_thinks_its_fullscreen_| is used when transitioning, and when | |
| 644 // the state change will not be made for some time. However, it is possible | |
| 645 // for a state update to be made before the final fullscreen state comes. | |
| 646 // In that case, |content_thinks_its_fullscreen_| will be cleared, but we | |
| 647 // will fall back to |IsFullscreen| which will soon have the correct state. | |
| 648 return content_thinks_its_fullscreen_ || IsFullscreen(); | |
| 649 } | |
| 650 | |
| 651 bool NativeAppWindowGtk::IsDetached() const { | |
| 652 return false; | |
| 653 } | |
| 654 | |
| 655 void NativeAppWindowGtk::UpdateWindowIcon() { | |
| 656 Profile* profile = | |
| 657 Profile::FromBrowserContext(app_window_->browser_context()); | |
| 658 gfx::Image app_icon = app_window_->app_icon(); | |
| 659 if (!app_icon.IsEmpty()) | |
| 660 gtk_util::SetWindowIcon(window_, profile, app_icon.ToGdkPixbuf()); | |
| 661 else | |
| 662 gtk_util::SetWindowIcon(window_, profile); | |
| 663 } | |
| 664 | |
| 665 void NativeAppWindowGtk::UpdateWindowTitle() { | |
| 666 base::string16 title = app_window_->GetTitle(); | |
| 667 gtk_window_set_title(window_, base::UTF16ToUTF8(title).c_str()); | |
| 668 } | |
| 669 | |
| 670 void NativeAppWindowGtk::UpdateBadgeIcon() { | |
| 671 NOTIMPLEMENTED(); | |
| 672 } | |
| 673 | |
| 674 void NativeAppWindowGtk::UpdateDraggableRegions( | |
| 675 const std::vector<extensions::DraggableRegion>& regions) { | |
| 676 // Draggable region is not supported for non-frameless window. | |
| 677 if (!frameless_) | |
| 678 return; | |
| 679 | |
| 680 draggable_region_.reset(AppWindow::RawDraggableRegionsToSkRegion(regions)); | |
| 681 } | |
| 682 | |
| 683 SkRegion* NativeAppWindowGtk::GetDraggableRegion() { | |
| 684 return draggable_region_.get(); | |
| 685 } | |
| 686 | |
| 687 void NativeAppWindowGtk::UpdateShape(scoped_ptr<SkRegion> region) { | |
| 688 NOTIMPLEMENTED(); | |
| 689 } | |
| 690 | |
| 691 void NativeAppWindowGtk::HandleKeyboardEvent( | |
| 692 const content::NativeWebKeyboardEvent& event) { | |
| 693 // No-op. | |
| 694 } | |
| 695 | |
| 696 bool NativeAppWindowGtk::IsFrameless() const { | |
| 697 return frameless_; | |
| 698 } | |
| 699 | |
| 700 bool NativeAppWindowGtk::HasFrameColor() const { | |
| 701 return false; | |
| 702 } | |
| 703 | |
| 704 SkColor NativeAppWindowGtk::FrameColor() const { | |
| 705 return SkColor(); | |
| 706 } | |
| 707 | |
| 708 gfx::Insets NativeAppWindowGtk::GetFrameInsets() const { | |
| 709 if (frameless_) | |
| 710 return gfx::Insets(); | |
| 711 GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); | |
| 712 if (!gdk_window) | |
| 713 return gfx::Insets(); | |
| 714 | |
| 715 gint current_width = 0; | |
| 716 gint current_height = 0; | |
| 717 gtk_window_get_size(window_, ¤t_width, ¤t_height); | |
| 718 gint current_x = 0; | |
| 719 gint current_y = 0; | |
| 720 gdk_window_get_position(gdk_window, ¤t_x, ¤t_y); | |
| 721 GdkRectangle rect_with_decorations = {0}; | |
| 722 gdk_window_get_frame_extents(gdk_window, | |
| 723 &rect_with_decorations); | |
| 724 | |
| 725 int left_inset = current_x - rect_with_decorations.x; | |
| 726 int top_inset = current_y - rect_with_decorations.y; | |
| 727 return gfx::Insets( | |
| 728 top_inset, | |
| 729 left_inset, | |
| 730 rect_with_decorations.height - current_height - top_inset, | |
| 731 rect_with_decorations.width - current_width - left_inset); | |
| 732 } | |
| 733 | |
| 734 void NativeAppWindowGtk::HideWithApp() {} | |
| 735 void NativeAppWindowGtk::ShowWithApp() {} | |
| 736 | |
| 737 void NativeAppWindowGtk::UpdateShelfMenu() { | |
| 738 // TODO(tmdiep): To be implemented for GTK. | |
| 739 NOTIMPLEMENTED(); | |
| 740 } | |
| 741 | |
| 742 gfx::Size NativeAppWindowGtk::GetContentMinimumSize() const { | |
| 743 return size_constraints_.GetMinimumSize(); | |
| 744 } | |
| 745 | |
| 746 gfx::Size NativeAppWindowGtk::GetContentMaximumSize() const { | |
| 747 return size_constraints_.GetMaximumSize(); | |
| 748 } | |
| 749 | |
| 750 void NativeAppWindowGtk::SetContentSizeConstraints( | |
| 751 const gfx::Size& min_size, const gfx::Size& max_size) { | |
| 752 bool changed = size_constraints_.GetMinimumSize() != min_size || | |
| 753 size_constraints_.GetMaximumSize() != max_size; | |
| 754 if (!changed) | |
| 755 return; | |
| 756 | |
| 757 size_constraints_.set_minimum_size(min_size); | |
| 758 size_constraints_.set_maximum_size(max_size); | |
| 759 UpdateContentMinMaxSize(); | |
| 760 } | |
| OLD | NEW |