OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
| 5 #include "chrome/browser/ui/panels/panel_gtk.h" |
| 6 |
| 7 #include <gdk/gdk.h> |
| 8 #include <gdk/gdkkeysyms.h> |
| 9 #include <X11/XF86keysym.h> |
| 10 |
| 11 #include "base/bind.h" |
| 12 #include "base/debug/trace_event.h" |
5 #include "base/logging.h" | 13 #include "base/logging.h" |
| 14 #include "base/message_loop.h" |
| 15 #include "base/utf_string_conversions.h" |
| 16 #include "chrome/app/chrome_command_ids.h" |
| 17 #include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog_queue.h" |
| 18 #include "chrome/browser/ui/gtk/custom_button.h" |
| 19 #include "chrome/browser/ui/gtk/gtk_theme_service.h" |
| 20 #include "chrome/browser/ui/gtk/gtk_util.h" |
| 21 #include "chrome/browser/ui/gtk/gtk_window_util.h" |
6 #include "chrome/browser/ui/panels/panel.h" | 22 #include "chrome/browser/ui/panels/panel.h" |
| 23 #include "chrome/browser/ui/panels/panel_bounds_animation.h" |
| 24 #include "chrome/browser/ui/panels/panel_titlebar_gtk.h" |
| 25 #include "chrome/browser/ui/panels/panel_constants.h" |
| 26 #include "chrome/browser/ui/panels/panel_drag_gtk.h" |
| 27 #include "chrome/browser/ui/panels/panel_manager.h" |
| 28 #include "chrome/browser/web_applications/web_app.h" |
| 29 #include "chrome/common/chrome_notification_types.h" |
| 30 #include "content/public/browser/native_web_keyboard_event.h" |
| 31 #include "content/public/browser/notification_service.h" |
| 32 #include "content/public/browser/web_contents.h" |
| 33 #include "grit/theme_resources.h" |
| 34 #include "grit/ui_resources.h" |
| 35 #include "ui/base/accelerators/accelerator_gtk.h" |
| 36 #include "ui/base/gtk/gtk_compat.h" |
| 37 #include "ui/base/gtk/gtk_expanded_container.h" |
| 38 #include "ui/base/gtk/gtk_hig_constants.h" |
| 39 #include "ui/base/x/active_window_watcher_x.h" |
| 40 #include "ui/gfx/canvas.h" |
| 41 #include "ui/gfx/image/cairo_cached_surface.h" |
| 42 #include "ui/gfx/image/image.h" |
| 43 |
| 44 using content::NativeWebKeyboardEvent; |
| 45 using content::WebContents; |
| 46 |
| 47 namespace { |
| 48 |
| 49 const char* kPanelWindowKey = "__PANEL_GTK__"; |
| 50 |
| 51 // The number of milliseconds between loading animation frames. |
| 52 const int kLoadingAnimationFrameTimeMs = 30; |
| 53 |
| 54 // The frame border is only visible in restored mode and is hardcoded to 4 px |
| 55 // on each side regardless of the system window border size. |
| 56 const int kFrameBorderThickness = 4; |
| 57 // While resize areas on Windows are normally the same size as the window |
| 58 // borders, our top area is shrunk by 1 px to make it easier to move the window |
| 59 // around with our thinner top grabbable strip. (Incidentally, our side and |
| 60 // bottom resize areas don't match the frame border thickness either -- they |
| 61 // span the whole nonclient area, so there's no "dead zone" for the mouse.) |
| 62 const int kTopResizeAdjust = 1; |
| 63 // In the window corners, the resize areas don't actually expand bigger, but |
| 64 // the 16 px at the end of each edge triggers diagonal resizing. |
| 65 const int kResizeAreaCornerSize = 16; |
| 66 |
| 67 // Colors used to draw frame background under default theme. |
| 68 const SkColor kActiveBackgroundDefaultColor = SkColorSetRGB(0x3a, 0x3d, 0x3d); |
| 69 const SkColor kInactiveBackgroundDefaultColor = SkColorSetRGB(0x7a, 0x7c, 0x7c); |
| 70 const SkColor kAttentionBackgroundDefaultColor = |
| 71 SkColorSetRGB(0xff, 0xab, 0x57); |
| 72 const SkColor kMinimizeBackgroundDefaultColor = SkColorSetRGB(0xf5, 0xf4, 0xf0); |
| 73 const SkColor kMinimizeBorderDefaultColor = SkColorSetRGB(0xc9, 0xc9, 0xc9); |
| 74 |
| 75 // Color used to draw the divider line between the titlebar and the client area. |
| 76 const SkColor kDividerColor = SkColorSetRGB(0x2a, 0x2c, 0x2c); |
| 77 |
| 78 // Set minimium width for window really small. |
| 79 const int kMinWindowWidth = 26; |
| 80 |
| 81 // Table of supported accelerators in Panels. |
| 82 const struct AcceleratorMapping { |
| 83 guint keyval; |
| 84 int command_id; |
| 85 GdkModifierType modifier_type; |
| 86 } kAcceleratorMap[] = { |
| 87 // Window controls. |
| 88 { GDK_w, IDC_CLOSE_WINDOW, GDK_CONTROL_MASK }, |
| 89 { GDK_w, IDC_CLOSE_WINDOW, |
| 90 GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) }, |
| 91 { GDK_q, IDC_EXIT, GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) }, |
| 92 |
| 93 // Zoom level. |
| 94 { GDK_KP_Add, IDC_ZOOM_PLUS, GDK_CONTROL_MASK }, |
| 95 { GDK_plus, IDC_ZOOM_PLUS, |
| 96 GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) }, |
| 97 { GDK_equal, IDC_ZOOM_PLUS, GDK_CONTROL_MASK }, |
| 98 { XF86XK_ZoomIn, IDC_ZOOM_PLUS, GdkModifierType(0) }, |
| 99 { GDK_KP_0, IDC_ZOOM_NORMAL, GDK_CONTROL_MASK }, |
| 100 { GDK_0, IDC_ZOOM_NORMAL, GDK_CONTROL_MASK }, |
| 101 { GDK_KP_Subtract, IDC_ZOOM_MINUS, GDK_CONTROL_MASK }, |
| 102 { GDK_minus, IDC_ZOOM_MINUS, GDK_CONTROL_MASK }, |
| 103 { GDK_underscore, IDC_ZOOM_MINUS, |
| 104 GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) }, |
| 105 { XF86XK_ZoomOut, IDC_ZOOM_MINUS, GdkModifierType(0) }, |
| 106 |
| 107 // Navigation. |
| 108 { GDK_Escape, IDC_STOP, GdkModifierType(0) }, |
| 109 { XF86XK_Stop, IDC_STOP, GdkModifierType(0) }, |
| 110 { GDK_r, IDC_RELOAD, GDK_CONTROL_MASK }, |
| 111 { GDK_r, IDC_RELOAD_IGNORING_CACHE, |
| 112 GdkModifierType(GDK_CONTROL_MASK|GDK_SHIFT_MASK) }, |
| 113 { GDK_F5, IDC_RELOAD, GdkModifierType(0) }, |
| 114 { GDK_F5, IDC_RELOAD_IGNORING_CACHE, GDK_CONTROL_MASK }, |
| 115 { GDK_F5, IDC_RELOAD_IGNORING_CACHE, GDK_SHIFT_MASK }, |
| 116 { XF86XK_Reload, IDC_RELOAD, GdkModifierType(0) }, |
| 117 { XF86XK_Refresh, IDC_RELOAD, GdkModifierType(0) }, |
| 118 |
| 119 // Editing. |
| 120 { GDK_c, IDC_COPY, GDK_CONTROL_MASK }, |
| 121 { GDK_x, IDC_CUT, GDK_CONTROL_MASK }, |
| 122 { GDK_v, IDC_PASTE, GDK_CONTROL_MASK }, |
| 123 }; |
| 124 |
| 125 // Table of accelerator mappings to command ids. |
| 126 typedef std::map<ui::AcceleratorGtk, int> AcceleratorGtkMap; |
| 127 |
| 128 const AcceleratorGtkMap& GetAcceleratorTable() { |
| 129 CR_DEFINE_STATIC_LOCAL(AcceleratorGtkMap, accelerator_table, ()); |
| 130 if (accelerator_table.empty()) { |
| 131 for (size_t i = 0; i < arraysize(kAcceleratorMap); ++i) { |
| 132 const AcceleratorMapping& entry = kAcceleratorMap[i]; |
| 133 ui::AcceleratorGtk accelerator(entry.keyval, entry.modifier_type); |
| 134 accelerator_table[accelerator] = entry.command_id; |
| 135 } |
| 136 } |
| 137 return accelerator_table; |
| 138 } |
| 139 |
| 140 gfx::Image* CreateImageForColor(SkColor color) { |
| 141 gfx::Canvas canvas(gfx::Size(1, 1), ui::SCALE_FACTOR_100P, true); |
| 142 canvas.DrawColor(color); |
| 143 return new gfx::Image(gfx::ImageSkia(canvas.ExtractImageRep())); |
| 144 } |
| 145 |
| 146 const gfx::Image* GetActiveBackgroundDefaultImage() { |
| 147 static gfx::Image* image = NULL; |
| 148 if (!image) |
| 149 image = CreateImageForColor(kActiveBackgroundDefaultColor); |
| 150 return image; |
| 151 } |
| 152 |
| 153 const gfx::Image* GetInactiveBackgroundDefaultImage() { |
| 154 static gfx::Image* image = NULL; |
| 155 if (!image) |
| 156 image = CreateImageForColor(kInactiveBackgroundDefaultColor); |
| 157 return image; |
| 158 } |
| 159 |
| 160 const gfx::Image* GetAttentionBackgroundDefaultImage() { |
| 161 static gfx::Image* image = NULL; |
| 162 if (!image) |
| 163 image = CreateImageForColor(kAttentionBackgroundDefaultColor); |
| 164 return image; |
| 165 } |
| 166 |
| 167 const gfx::Image* GetMinimizeBackgroundDefaultImage() { |
| 168 static gfx::Image* image = NULL; |
| 169 if (!image) |
| 170 image = CreateImageForColor(kMinimizeBackgroundDefaultColor); |
| 171 return image; |
| 172 } |
| 173 |
| 174 // Used to stash a pointer to the Panel window inside the native |
| 175 // Gtk window for retrieval in static callbacks. |
| 176 GQuark GetPanelWindowQuarkKey() { |
| 177 static GQuark quark = g_quark_from_static_string(kPanelWindowKey); |
| 178 return quark; |
| 179 } |
| 180 |
| 181 // Size of window frame. Empty until first panel has been allocated |
| 182 // and sized. Frame size won't change for other panels so it can be |
| 183 // computed once for all panels. |
| 184 gfx::Size& GetFrameSize() { |
| 185 CR_DEFINE_STATIC_LOCAL(gfx::Size, frame_size, ()); |
| 186 return frame_size; |
| 187 } |
| 188 |
| 189 void SetFrameSize(const gfx::Size& new_size) { |
| 190 gfx::Size& frame_size = GetFrameSize(); |
| 191 frame_size.SetSize(new_size.width(), new_size.height()); |
| 192 } |
| 193 |
| 194 } |
7 | 195 |
8 // static | 196 // static |
9 NativePanel* Panel::CreateNativePanel(Panel* panel, const gfx::Rect& bounds) { | 197 NativePanel* Panel::CreateNativePanel(Panel* panel, const gfx::Rect& bounds) { |
10 NOTIMPLEMENTED(); | 198 PanelGtk* panel_gtk = new PanelGtk(panel, bounds); |
11 return NULL; | 199 panel_gtk->Init(); |
12 } | 200 return panel_gtk; |
| 201 } |
| 202 |
| 203 PanelGtk::PanelGtk(Panel* panel, const gfx::Rect& bounds) |
| 204 : panel_(panel), |
| 205 bounds_(bounds), |
| 206 is_shown_(false), |
| 207 paint_state_(PAINT_AS_INACTIVE), |
| 208 is_drawing_attention_(false), |
| 209 frame_cursor_(NULL), |
| 210 is_active_(!ui::ActiveWindowWatcherX::WMSupportsActivation()), |
| 211 window_(NULL), |
| 212 window_container_(NULL), |
| 213 window_vbox_(NULL), |
| 214 render_area_event_box_(NULL), |
| 215 contents_expanded_(NULL), |
| 216 accel_group_(NULL) { |
| 217 } |
| 218 |
| 219 PanelGtk::~PanelGtk() { |
| 220 ui::ActiveWindowWatcherX::RemoveObserver(this); |
| 221 } |
| 222 |
| 223 void PanelGtk::Init() { |
| 224 ui::ActiveWindowWatcherX::AddObserver(this); |
| 225 |
| 226 window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL)); |
| 227 g_object_set_qdata(G_OBJECT(window_), GetPanelWindowQuarkKey(), this); |
| 228 gtk_widget_add_events(GTK_WIDGET(window_), GDK_BUTTON_PRESS_MASK | |
| 229 GDK_POINTER_MOTION_MASK); |
| 230 gtk_window_set_decorated(window_, false); |
| 231 // Keep the window always on top. |
| 232 gtk_window_set_keep_above(window_, TRUE); |
| 233 // Show the window on all the virtual desktops. |
| 234 gtk_window_stick(window_); |
| 235 // Do not show an icon in the task bar. Window operations such as close, |
| 236 // minimize etc. can only be done from the panel UI. |
| 237 gtk_window_set_skip_taskbar_hint(window_, TRUE); |
| 238 |
| 239 // Disable the resize gripper on Ubuntu. |
| 240 gtk_window_util::DisableResizeGrip(window_); |
| 241 |
| 242 // Add this window to its own unique window group to allow for |
| 243 // window-to-parent modality. |
| 244 gtk_window_group_add_window(gtk_window_group_new(), window_); |
| 245 g_object_unref(gtk_window_get_group(window_)); |
| 246 |
| 247 // Set minimum height for the window. |
| 248 GdkGeometry hints; |
| 249 hints.min_height = panel::kMinimizedPanelHeight; |
| 250 hints.min_width = kMinWindowWidth; |
| 251 gtk_window_set_geometry_hints( |
| 252 window_, GTK_WIDGET(window_), &hints, GDK_HINT_MIN_SIZE); |
| 253 |
| 254 // Connect signal handlers to the window. |
| 255 g_signal_connect(window_, "delete-event", |
| 256 G_CALLBACK(OnMainWindowDeleteEventThunk), this); |
| 257 g_signal_connect(window_, "destroy", |
| 258 G_CALLBACK(OnMainWindowDestroyThunk), this); |
| 259 g_signal_connect(window_, "configure-event", |
| 260 G_CALLBACK(OnConfigureThunk), this); |
| 261 g_signal_connect(window_, "key-press-event", |
| 262 G_CALLBACK(OnKeyPressThunk), this); |
| 263 g_signal_connect(window_, "motion-notify-event", |
| 264 G_CALLBACK(OnMouseMoveEventThunk), this); |
| 265 g_signal_connect(window_, "button-press-event", |
| 266 G_CALLBACK(OnButtonPressEventThunk), this); |
| 267 |
| 268 // This vbox contains the titlebar and the render area, but not |
| 269 // the custom frame border. |
| 270 window_vbox_ = gtk_vbox_new(FALSE, 0); |
| 271 gtk_widget_show(window_vbox_); |
| 272 |
| 273 // TODO(jennb): add GlobalMenuBar after refactoring out Browser. |
| 274 |
| 275 // The window container draws the custom browser frame. |
| 276 window_container_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0); |
| 277 gtk_widget_set_name(window_container_, "chrome-custom-frame-border"); |
| 278 gtk_widget_set_app_paintable(window_container_, TRUE); |
| 279 gtk_widget_set_double_buffered(window_container_, FALSE); |
| 280 gtk_widget_set_redraw_on_allocate(window_container_, TRUE); |
| 281 gtk_alignment_set_padding(GTK_ALIGNMENT(window_container_), 1, |
| 282 kFrameBorderThickness, kFrameBorderThickness, kFrameBorderThickness); |
| 283 g_signal_connect(window_container_, "expose-event", |
| 284 G_CALLBACK(OnCustomFrameExposeThunk), this); |
| 285 gtk_container_add(GTK_CONTAINER(window_container_), window_vbox_); |
| 286 |
| 287 // Build the titlebar. |
| 288 titlebar_.reset(new PanelTitlebarGtk(this)); |
| 289 titlebar_->Init(); |
| 290 gtk_box_pack_start(GTK_BOX(window_vbox_), titlebar_->widget(), FALSE, FALSE, |
| 291 0); |
| 292 g_signal_connect(titlebar_->widget(), "button-press-event", |
| 293 G_CALLBACK(OnTitlebarButtonPressEventThunk), this); |
| 294 g_signal_connect(titlebar_->widget(), "button-release-event", |
| 295 G_CALLBACK(OnTitlebarButtonReleaseEventThunk), this); |
| 296 |
| 297 contents_expanded_ = gtk_expanded_container_new(); |
| 298 gtk_widget_show(contents_expanded_); |
| 299 |
| 300 render_area_event_box_ = gtk_event_box_new(); |
| 301 // Set a white background so during startup the user sees white in the |
| 302 // content area before we get a WebContents in place. |
| 303 gtk_widget_modify_bg(render_area_event_box_, GTK_STATE_NORMAL, |
| 304 &ui::kGdkWhite); |
| 305 gtk_container_add(GTK_CONTAINER(render_area_event_box_), |
| 306 contents_expanded_); |
| 307 gtk_widget_show(render_area_event_box_); |
| 308 gtk_box_pack_end(GTK_BOX(window_vbox_), render_area_event_box_, |
| 309 TRUE, TRUE, 0); |
| 310 |
| 311 gtk_container_add(GTK_CONTAINER(window_), window_container_); |
| 312 gtk_widget_show(window_container_); |
| 313 |
| 314 ConnectAccelerators(); |
| 315 } |
| 316 |
| 317 void PanelGtk::UpdateWindowShape(int width, int height) { |
| 318 // For panels, only top corners are rounded. The bottom corners are not |
| 319 // rounded because panels are aligned to the bottom edge of the screen. |
| 320 GdkRectangle top_top_rect = { 3, 0, width - 6, 1 }; |
| 321 GdkRectangle top_mid_rect = { 1, 1, width - 2, 2 }; |
| 322 GdkRectangle mid_rect = { 0, 3, width, height - 3 }; |
| 323 GdkRegion* mask = gdk_region_rectangle(&top_top_rect); |
| 324 gdk_region_union_with_rect(mask, &top_mid_rect); |
| 325 gdk_region_union_with_rect(mask, &mid_rect); |
| 326 gdk_window_shape_combine_region( |
| 327 gtk_widget_get_window(GTK_WIDGET(window_)), mask, 0, 0); |
| 328 if (mask) |
| 329 gdk_region_destroy(mask); |
| 330 } |
| 331 |
| 332 gboolean PanelGtk::OnConfigure(GtkWidget* widget, |
| 333 GdkEventConfigure* event) { |
| 334 // When the window moves, we'll get multiple configure-event signals. We can |
| 335 // also get events when the bounds haven't changed, but the window's stacking |
| 336 // has, which we aren't interested in. http://crbug.com/70125 |
| 337 gfx::Size new_size(event->width, event->height); |
| 338 if (new_size == configure_size_) |
| 339 return FALSE; |
| 340 |
| 341 UpdateWindowShape(event->width, event->height); |
| 342 configure_size_ = new_size; |
| 343 |
| 344 if (!GetFrameSize().IsEmpty()) |
| 345 return FALSE; |
| 346 |
| 347 // Save the frame size allocated by the system after as the |
| 348 // frame size will be affected when we shrink the panel smaller |
| 349 // than the frame (e.g. when the panel is minimized). |
| 350 SetFrameSize(GetNonClientFrameSize()); |
| 351 panel_->OnWindowSizeAvailable(); |
| 352 |
| 353 content::NotificationService::current()->Notify( |
| 354 chrome::NOTIFICATION_PANEL_WINDOW_SIZE_KNOWN, |
| 355 content::Source<Panel>(panel_.get()), |
| 356 content::NotificationService::NoDetails()); |
| 357 |
| 358 return FALSE; |
| 359 } |
| 360 |
| 361 void PanelGtk::ConnectAccelerators() { |
| 362 accel_group_ = gtk_accel_group_new(); |
| 363 gtk_window_add_accel_group(window_, accel_group_); |
| 364 |
| 365 const AcceleratorGtkMap& accelerator_table = GetAcceleratorTable(); |
| 366 for (AcceleratorGtkMap::const_iterator iter = accelerator_table.begin(); |
| 367 iter != accelerator_table.end(); ++iter) { |
| 368 gtk_accel_group_connect( |
| 369 accel_group_, |
| 370 iter->first.GetGdkKeyCode(), |
| 371 static_cast<GdkModifierType>(iter->first.modifiers()), |
| 372 GtkAccelFlags(0), |
| 373 g_cclosure_new(G_CALLBACK(OnGtkAccelerator), |
| 374 GINT_TO_POINTER(iter->second), NULL)); |
| 375 } |
| 376 } |
| 377 |
| 378 void PanelGtk::DisconnectAccelerators() { |
| 379 // Disconnecting the keys we connected to our accelerator group frees the |
| 380 // closures allocated in ConnectAccelerators. |
| 381 const AcceleratorGtkMap& accelerator_table = GetAcceleratorTable(); |
| 382 for (AcceleratorGtkMap::const_iterator iter = accelerator_table.begin(); |
| 383 iter != accelerator_table.end(); ++iter) { |
| 384 gtk_accel_group_disconnect_key(accel_group_, |
| 385 iter->first.GetGdkKeyCode(), |
| 386 static_cast<GdkModifierType>(iter->first.modifiers())); |
| 387 } |
| 388 gtk_window_remove_accel_group(window_, accel_group_); |
| 389 g_object_unref(accel_group_); |
| 390 accel_group_ = NULL; |
| 391 } |
| 392 |
| 393 // static |
| 394 gboolean PanelGtk::OnGtkAccelerator(GtkAccelGroup* accel_group, |
| 395 GObject* acceleratable, |
| 396 guint keyval, |
| 397 GdkModifierType modifier, |
| 398 void* user_data) { |
| 399 DCHECK(acceleratable); |
| 400 int command_id = GPOINTER_TO_INT(user_data); |
| 401 PanelGtk* panel_gtk = static_cast<PanelGtk*>( |
| 402 g_object_get_qdata(acceleratable, GetPanelWindowQuarkKey())); |
| 403 return panel_gtk->panel()->ExecuteCommandIfEnabled(command_id); |
| 404 } |
| 405 |
| 406 gboolean PanelGtk::OnKeyPress(GtkWidget* widget, GdkEventKey* event) { |
| 407 // No way to deactivate a window in GTK, so ignore input if window |
| 408 // is supposed to be 'inactive'. See comments in DeactivatePanel(). |
| 409 if (!is_active_) |
| 410 return TRUE; |
| 411 |
| 412 // Propagate the key event to child widget first, so we don't override |
| 413 // their accelerators. |
| 414 if (!gtk_window_propagate_key_event(GTK_WINDOW(widget), event)) { |
| 415 if (!gtk_window_activate_key(GTK_WINDOW(widget), event)) { |
| 416 gtk_bindings_activate_event(GTK_OBJECT(widget), event); |
| 417 } |
| 418 } |
| 419 return TRUE; |
| 420 } |
| 421 |
| 422 bool PanelGtk::UsingDefaultTheme() const { |
| 423 // No theme is provided for attention painting. |
| 424 if (paint_state_ == PAINT_FOR_ATTENTION) |
| 425 return true; |
| 426 |
| 427 GtkThemeService* theme_provider = GtkThemeService::GetFrom(panel_->profile()); |
| 428 return theme_provider->UsingDefaultTheme() || |
| 429 theme_provider->UsingNativeTheme(); |
| 430 } |
| 431 |
| 432 bool PanelGtk::GetWindowEdge(int x, int y, GdkWindowEdge* edge) const { |
| 433 // Only detect the window edge when panels can be resized by the user. |
| 434 // This method is used by the base class to detect when the cursor has |
| 435 // hit the window edge in order to change the cursor to a resize cursor |
| 436 // and to detect when to initiate a resize drag. |
| 437 panel::Resizability resizability = panel_->CanResizeByMouse(); |
| 438 if (panel::NOT_RESIZABLE == resizability) |
| 439 return false; |
| 440 |
| 441 if (x < kFrameBorderThickness) { |
| 442 // Left edge. |
| 443 if (y < kResizeAreaCornerSize - kTopResizeAdjust) { |
| 444 *edge = GDK_WINDOW_EDGE_NORTH_WEST; |
| 445 } else if (y < bounds_.height() - kResizeAreaCornerSize) { |
| 446 *edge = GDK_WINDOW_EDGE_WEST; |
| 447 } else { |
| 448 *edge = GDK_WINDOW_EDGE_SOUTH_WEST; |
| 449 } |
| 450 } else if (x < bounds_.width() - kFrameBorderThickness) { |
| 451 if (y < kFrameBorderThickness - kTopResizeAdjust) { |
| 452 // Top edge. |
| 453 if (x < kResizeAreaCornerSize) { |
| 454 *edge = GDK_WINDOW_EDGE_NORTH_WEST; |
| 455 } else if (x < bounds_.width() - kResizeAreaCornerSize) { |
| 456 *edge = GDK_WINDOW_EDGE_NORTH; |
| 457 } else { |
| 458 *edge = GDK_WINDOW_EDGE_NORTH_EAST; |
| 459 } |
| 460 } else if (y < bounds_.height() - kFrameBorderThickness) { |
| 461 // Ignore the middle content area. |
| 462 return false; |
| 463 } else { |
| 464 // Bottom edge. |
| 465 if (x < kResizeAreaCornerSize) { |
| 466 *edge = GDK_WINDOW_EDGE_SOUTH_WEST; |
| 467 } else if (x < bounds_.width() - kResizeAreaCornerSize) { |
| 468 *edge = GDK_WINDOW_EDGE_SOUTH; |
| 469 } else { |
| 470 *edge = GDK_WINDOW_EDGE_SOUTH_EAST; |
| 471 } |
| 472 } |
| 473 } else { |
| 474 // Right edge. |
| 475 if (y < kResizeAreaCornerSize - kTopResizeAdjust) { |
| 476 *edge = GDK_WINDOW_EDGE_NORTH_EAST; |
| 477 } else if (y < bounds_.height() - kResizeAreaCornerSize) { |
| 478 *edge = GDK_WINDOW_EDGE_EAST; |
| 479 } else { |
| 480 *edge = GDK_WINDOW_EDGE_SOUTH_EAST; |
| 481 } |
| 482 } |
| 483 |
| 484 // Special handling if bottom edge is not resizable. |
| 485 if (panel::RESIZABLE_ALL_SIDES_EXCEPT_BOTTOM == resizability) { |
| 486 if (*edge == GDK_WINDOW_EDGE_SOUTH) |
| 487 return FALSE; |
| 488 if (*edge == GDK_WINDOW_EDGE_SOUTH_WEST) |
| 489 *edge = GDK_WINDOW_EDGE_WEST; |
| 490 else if (*edge == GDK_WINDOW_EDGE_SOUTH_EAST) |
| 491 *edge = GDK_WINDOW_EDGE_EAST; |
| 492 } |
| 493 |
| 494 return true; |
| 495 } |
| 496 |
| 497 const gfx::Image* PanelGtk::GetFrameBackground() const { |
| 498 return UsingDefaultTheme() ? |
| 499 GetDefaultFrameBackground() : GetThemedFrameBackground(); |
| 500 } |
| 501 |
| 502 const gfx::Image* PanelGtk::GetDefaultFrameBackground() const { |
| 503 switch (paint_state_) { |
| 504 case PAINT_AS_INACTIVE: |
| 505 return GetInactiveBackgroundDefaultImage(); |
| 506 case PAINT_AS_ACTIVE: |
| 507 return GetActiveBackgroundDefaultImage(); |
| 508 case PAINT_AS_MINIMIZED: |
| 509 return GetMinimizeBackgroundDefaultImage(); |
| 510 case PAINT_FOR_ATTENTION: |
| 511 return GetAttentionBackgroundDefaultImage(); |
| 512 default: |
| 513 NOTREACHED(); |
| 514 return GetInactiveBackgroundDefaultImage(); |
| 515 } |
| 516 } |
| 517 |
| 518 const gfx::Image* PanelGtk::GetThemedFrameBackground() const { |
| 519 GtkThemeService* theme_provider = GtkThemeService::GetFrom(panel_->profile()); |
| 520 return theme_provider->GetImageNamed(paint_state_ == PAINT_AS_ACTIVE ? |
| 521 IDR_THEME_TOOLBAR : IDR_THEME_TAB_BACKGROUND); |
| 522 } |
| 523 |
| 524 gboolean PanelGtk::OnCustomFrameExpose(GtkWidget* widget, |
| 525 GdkEventExpose* event) { |
| 526 TRACE_EVENT0("ui::gtk", "PanelGtk::OnCustomFrameExpose"); |
| 527 cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget)); |
| 528 gdk_cairo_rectangle(cr, &event->area); |
| 529 cairo_clip(cr); |
| 530 |
| 531 // Update the painting state. |
| 532 int window_height = gdk_window_get_height(gtk_widget_get_window(widget)); |
| 533 if (is_drawing_attention_) |
| 534 paint_state_ = PAINT_FOR_ATTENTION; |
| 535 else if (window_height <= panel::kMinimizedPanelHeight) |
| 536 paint_state_ = PAINT_AS_MINIMIZED; |
| 537 else if (is_active_) |
| 538 paint_state_ = PAINT_AS_ACTIVE; |
| 539 else |
| 540 paint_state_ = PAINT_AS_INACTIVE; |
| 541 |
| 542 // Draw the background. |
| 543 gfx::CairoCachedSurface* surface = GetFrameBackground()->ToCairo(); |
| 544 surface->SetSource(cr, widget, 0, 0); |
| 545 cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT); |
| 546 cairo_rectangle(cr, event->area.x, event->area.y, |
| 547 event->area.width, event->area.height); |
| 548 cairo_fill(cr); |
| 549 |
| 550 // Draw the divider only if we're showing more than titlebar. |
| 551 if (window_height > panel::kTitlebarHeight) { |
| 552 cairo_set_source_rgb(cr, |
| 553 SkColorGetR(kDividerColor) / 255.0, |
| 554 SkColorGetG(kDividerColor) / 255.0, |
| 555 SkColorGetB(kDividerColor) / 255.0); |
| 556 cairo_rectangle(cr, 0, panel::kTitlebarHeight - 1, bounds_.width(), 1); |
| 557 cairo_fill(cr); |
| 558 } |
| 559 |
| 560 // Draw the border for the minimized panel only. |
| 561 if (paint_state_ == PAINT_AS_MINIMIZED) { |
| 562 cairo_move_to(cr, 0, 3); |
| 563 cairo_line_to(cr, 1, 2); |
| 564 cairo_line_to(cr, 1, 1); |
| 565 cairo_line_to(cr, 2, 1); |
| 566 cairo_line_to(cr, 3, 0); |
| 567 cairo_line_to(cr, event->area.width - 3, 0); |
| 568 cairo_line_to(cr, event->area.width - 2, 1); |
| 569 cairo_line_to(cr, event->area.width - 1, 1); |
| 570 cairo_line_to(cr, event->area.width - 1, 2); |
| 571 cairo_line_to(cr, event->area.width - 1, 3); |
| 572 cairo_line_to(cr, event->area.width - 1, event->area.height - 1); |
| 573 cairo_line_to(cr, 0, event->area.height - 1); |
| 574 cairo_close_path(cr); |
| 575 cairo_set_source_rgb(cr, |
| 576 SkColorGetR(kMinimizeBorderDefaultColor) / 255.0, |
| 577 SkColorGetG(kMinimizeBorderDefaultColor) / 255.0, |
| 578 SkColorGetB(kMinimizeBorderDefaultColor) / 255.0); |
| 579 cairo_set_line_width(cr, 1.0); |
| 580 cairo_stroke(cr); |
| 581 } |
| 582 |
| 583 cairo_destroy(cr); |
| 584 |
| 585 return FALSE; // Allow subwidgets to paint. |
| 586 } |
| 587 |
| 588 void PanelGtk::EnsureDragHelperCreated() { |
| 589 if (drag_helper_.get()) |
| 590 return; |
| 591 |
| 592 drag_helper_.reset(new PanelDragGtk(panel_.get())); |
| 593 gtk_box_pack_end(GTK_BOX(window_vbox_), drag_helper_->widget(), |
| 594 FALSE, FALSE, 0); |
| 595 } |
| 596 |
| 597 gboolean PanelGtk::OnTitlebarButtonPressEvent( |
| 598 GtkWidget* widget, GdkEventButton* event) { |
| 599 if (event->button != 1) |
| 600 return TRUE; |
| 601 if (event->type != GDK_BUTTON_PRESS) |
| 602 return TRUE; |
| 603 |
| 604 gdk_window_raise(gtk_widget_get_window(GTK_WIDGET(window_))); |
| 605 EnsureDragHelperCreated(); |
| 606 drag_helper_->InitialTitlebarMousePress(event, titlebar_->widget()); |
| 607 return TRUE; |
| 608 } |
| 609 |
| 610 gboolean PanelGtk::OnTitlebarButtonReleaseEvent( |
| 611 GtkWidget* widget, GdkEventButton* event) { |
| 612 if (event->button != 1) |
| 613 return TRUE; |
| 614 |
| 615 panel_->OnTitlebarClicked((event->state & GDK_CONTROL_MASK) ? |
| 616 panel::APPLY_TO_ALL : panel::NO_MODIFIER); |
| 617 return TRUE; |
| 618 } |
| 619 |
| 620 gboolean PanelGtk::OnMouseMoveEvent(GtkWidget* widget, |
| 621 GdkEventMotion* event) { |
| 622 // This method is used to update the mouse cursor when over the edge of the |
| 623 // custom frame. If we're over some other widget, do nothing. |
| 624 if (event->window != gtk_widget_get_window(widget)) { |
| 625 // Reset the cursor. |
| 626 if (frame_cursor_) { |
| 627 frame_cursor_ = NULL; |
| 628 gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), NULL); |
| 629 } |
| 630 return FALSE; |
| 631 } |
| 632 |
| 633 // Update the cursor if we're on the custom frame border. |
| 634 GdkWindowEdge edge; |
| 635 bool has_hit_edge = GetWindowEdge(static_cast<int>(event->x), |
| 636 static_cast<int>(event->y), &edge); |
| 637 GdkCursorType new_cursor = has_hit_edge ? |
| 638 gtk_window_util::GdkWindowEdgeToGdkCursorType(edge) : GDK_LAST_CURSOR; |
| 639 GdkCursorType last_cursor = |
| 640 frame_cursor_ ? frame_cursor_->type : GDK_LAST_CURSOR; |
| 641 |
| 642 if (last_cursor != new_cursor) { |
| 643 frame_cursor_ = has_hit_edge ? gfx::GetCursor(new_cursor) : NULL; |
| 644 gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), |
| 645 frame_cursor_); |
| 646 } |
| 647 return FALSE; |
| 648 } |
| 649 |
| 650 gboolean PanelGtk::OnButtonPressEvent(GtkWidget* widget, |
| 651 GdkEventButton* event) { |
| 652 if (event->button != 1 || event->type != GDK_BUTTON_PRESS) |
| 653 return FALSE; |
| 654 |
| 655 // No way to deactivate a window in GTK, so we pretended it is deactivated. |
| 656 // See comments in DeactivatePanel(). |
| 657 // Mouse click anywhere in window should re-activate window so do it now. |
| 658 if (!is_active_) |
| 659 panel_->Activate(); |
| 660 |
| 661 // Make the button press coordinate relative to the panel window. |
| 662 int win_x, win_y; |
| 663 GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_)); |
| 664 gdk_window_get_origin(gdk_window, &win_x, &win_y); |
| 665 |
| 666 GdkWindowEdge edge; |
| 667 gfx::Point point(static_cast<int>(event->x_root - win_x), |
| 668 static_cast<int>(event->y_root - win_y)); |
| 669 bool has_hit_edge = GetWindowEdge(point.x(), point.y(), &edge); |
| 670 if (has_hit_edge) { |
| 671 gdk_window_raise(gdk_window); |
| 672 EnsureDragHelperCreated(); |
| 673 // Resize cursor was set by PanelGtk when mouse moved over window edge. |
| 674 GdkCursor* cursor = |
| 675 gdk_window_get_cursor(gtk_widget_get_window(GTK_WIDGET(window_))); |
| 676 drag_helper_->InitialWindowEdgeMousePress(event, cursor, edge); |
| 677 return TRUE; |
| 678 } |
| 679 |
| 680 return FALSE; // Continue to propagate the event. |
| 681 } |
| 682 |
| 683 void PanelGtk::ActiveWindowChanged(GdkWindow* active_window) { |
| 684 // Do nothing if we're in the process of closing the browser window. |
| 685 if (!window_) |
| 686 return; |
| 687 |
| 688 bool is_active = gtk_widget_get_window(GTK_WIDGET(window_)) == active_window; |
| 689 if (is_active == is_active_) |
| 690 return; // State did not change. |
| 691 |
| 692 if (is_active) { |
| 693 // If there's an app modal dialog (e.g., JS alert), try to redirect |
| 694 // the user's attention to the window owning the dialog. |
| 695 if (AppModalDialogQueue::GetInstance()->HasActiveDialog()) { |
| 696 AppModalDialogQueue::GetInstance()->ActivateModalDialog(); |
| 697 return; |
| 698 } |
| 699 } |
| 700 |
| 701 is_active_ = is_active; |
| 702 titlebar_->UpdateTextColor(); |
| 703 InvalidateWindow(); |
| 704 panel_->OnActiveStateChanged(is_active_); |
| 705 } |
| 706 |
| 707 // Callback for the delete event. This event is fired when the user tries to |
| 708 // close the window. |
| 709 gboolean PanelGtk::OnMainWindowDeleteEvent(GtkWidget* widget, |
| 710 GdkEvent* event) { |
| 711 ClosePanel(); |
| 712 |
| 713 // Return true to prevent the gtk window from being destroyed. Close will |
| 714 // destroy it for us. |
| 715 return TRUE; |
| 716 } |
| 717 |
| 718 void PanelGtk::OnMainWindowDestroy(GtkWidget* widget) { |
| 719 // BUG 8712. When we gtk_widget_destroy() in ClosePanel(), this will emit the |
| 720 // signal right away, and we will be here (while ClosePanel() is still in the |
| 721 // call stack). Let stack unwind before deleting the panel. |
| 722 // |
| 723 // We don't want to use DeleteSoon() here since it won't work on a nested pump |
| 724 // (like in UI tests). |
| 725 MessageLoop::current()->PostTask( |
| 726 FROM_HERE, base::Bind(&base::DeletePointer<PanelGtk>, this)); |
| 727 } |
| 728 |
| 729 void PanelGtk::ShowPanel() { |
| 730 gtk_window_present(window_); |
| 731 RevealPanel(); |
| 732 } |
| 733 |
| 734 void PanelGtk::ShowPanelInactive() { |
| 735 gtk_window_set_focus_on_map(window_, false); |
| 736 gtk_widget_show(GTK_WIDGET(window_)); |
| 737 RevealPanel(); |
| 738 } |
| 739 |
| 740 void PanelGtk::RevealPanel() { |
| 741 DCHECK(!is_shown_); |
| 742 is_shown_ = true; |
| 743 |
| 744 // Grow the window from the botttom up to produce a 'reveal' animation. |
| 745 int top = bounds_.bottom() - configure_size_.height(); |
| 746 StartBoundsAnimation( |
| 747 gfx::Rect(bounds_.x(), top, bounds_.width(), configure_size_.height()), |
| 748 bounds_); |
| 749 } |
| 750 |
| 751 gfx::Rect PanelGtk::GetPanelBounds() const { |
| 752 return bounds_; |
| 753 } |
| 754 |
| 755 void PanelGtk::SetPanelBounds(const gfx::Rect& bounds) { |
| 756 SetBoundsInternal(bounds, true); |
| 757 } |
| 758 |
| 759 void PanelGtk::SetPanelBoundsInstantly(const gfx::Rect& bounds) { |
| 760 SetBoundsInternal(bounds, false); |
| 761 } |
| 762 |
| 763 void PanelGtk::SetBoundsInternal(const gfx::Rect& bounds, bool animate) { |
| 764 if (bounds == bounds_) |
| 765 return; |
| 766 |
| 767 if (!animate) { |
| 768 // If no animation is in progress, apply bounds change instantly. Otherwise, |
| 769 // continue the animation with new target bounds. |
| 770 if (!IsAnimatingBounds()) |
| 771 gdk_window_move_resize(gtk_widget_get_window(GTK_WIDGET(window_)), |
| 772 bounds.x(), bounds.y(), |
| 773 bounds.width(), bounds.height()); |
| 774 } else if (is_shown_) { |
| 775 StartBoundsAnimation(bounds_, bounds); |
| 776 } |
| 777 |
| 778 bounds_ = bounds; |
| 779 } |
| 780 |
| 781 void PanelGtk::ClosePanel() { |
| 782 // We're already closing. Do nothing. |
| 783 if (!window_) |
| 784 return; |
| 785 |
| 786 if (!panel_->ShouldCloseWindow()) |
| 787 return; |
| 788 |
| 789 if (bounds_animator_.get()) |
| 790 bounds_animator_.reset(); |
| 791 |
| 792 if (drag_helper_.get()) |
| 793 drag_helper_.reset(); |
| 794 |
| 795 if (accel_group_) |
| 796 DisconnectAccelerators(); |
| 797 |
| 798 // Cancel any pending callback from the loading animation timer. |
| 799 loading_animation_timer_.Stop(); |
| 800 |
| 801 if (panel_->GetWebContents()) { |
| 802 // Hide the window (so it appears to have closed immediately). |
| 803 // When web contents are destroyed, we will be called back again. |
| 804 gtk_widget_hide(GTK_WIDGET(window_)); |
| 805 panel_->OnWindowClosing(); |
| 806 return; |
| 807 } |
| 808 |
| 809 GtkWidget* window = GTK_WIDGET(window_); |
| 810 // To help catch bugs in any event handlers that might get fired during the |
| 811 // destruction, set window_ to NULL before any handlers will run. |
| 812 window_ = NULL; |
| 813 |
| 814 panel_->OnNativePanelClosed(); |
| 815 |
| 816 // We don't want GlobalMenuBar handling any notifications or commands after |
| 817 // the window is destroyed. |
| 818 // TODO(jennb): global_menu_bar_->Disable(); |
| 819 gtk_widget_destroy(window); |
| 820 } |
| 821 |
| 822 void PanelGtk::ActivatePanel() { |
| 823 gtk_window_present(window_); |
| 824 } |
| 825 |
| 826 void PanelGtk::DeactivatePanel() { |
| 827 gdk_window_lower(gtk_widget_get_window(GTK_WIDGET(window_))); |
| 828 |
| 829 // Per ICCCM: http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.7 |
| 830 // A convention is also required for clients that want to give up the |
| 831 // input focus. There is no safe value set for them to set the input |
| 832 // focus to; therefore, they should ignore input material. |
| 833 // |
| 834 // No way to deactive a GTK window. Pretend panel is deactivated |
| 835 // and ignore input. |
| 836 ActiveWindowChanged(NULL); |
| 837 } |
| 838 |
| 839 bool PanelGtk::IsPanelActive() const { |
| 840 return is_active_; |
| 841 } |
| 842 |
| 843 void PanelGtk::PreventActivationByOS(bool prevent_activation) { |
| 844 gtk_window_set_accept_focus(window_, !prevent_activation); |
| 845 } |
| 846 |
| 847 gfx::NativeWindow PanelGtk::GetNativePanelHandle() { |
| 848 return window_; |
| 849 } |
| 850 |
| 851 void PanelGtk::UpdatePanelTitleBar() { |
| 852 TRACE_EVENT0("ui::gtk", "PanelGtk::UpdatePanelTitleBar"); |
| 853 string16 title = panel_->GetWindowTitle(); |
| 854 gtk_window_set_title(window_, UTF16ToUTF8(title).c_str()); |
| 855 titlebar_->UpdateTitleAndIcon(); |
| 856 } |
| 857 |
| 858 void PanelGtk::UpdatePanelLoadingAnimations(bool should_animate) { |
| 859 if (should_animate) { |
| 860 if (!loading_animation_timer_.IsRunning()) { |
| 861 // Loads are happening, and the timer isn't running, so start it. |
| 862 loading_animation_timer_.Start(FROM_HERE, |
| 863 base::TimeDelta::FromMilliseconds(kLoadingAnimationFrameTimeMs), |
| 864 this, |
| 865 &PanelGtk::LoadingAnimationCallback); |
| 866 } |
| 867 } else { |
| 868 if (loading_animation_timer_.IsRunning()) { |
| 869 loading_animation_timer_.Stop(); |
| 870 // Loads are now complete, update the state if a task was scheduled. |
| 871 LoadingAnimationCallback(); |
| 872 } |
| 873 } |
| 874 } |
| 875 |
| 876 void PanelGtk::LoadingAnimationCallback() { |
| 877 titlebar_->UpdateThrobber(panel_->GetWebContents()); |
| 878 } |
| 879 |
| 880 FindBar* PanelGtk::CreatePanelFindBar() { |
| 881 return NULL; // legacy |
| 882 } |
| 883 |
| 884 void PanelGtk::NotifyPanelOnUserChangedTheme() { |
| 885 titlebar_->UpdateTextColor(); |
| 886 InvalidateWindow(); |
| 887 } |
| 888 |
| 889 void PanelGtk::PanelCut() { |
| 890 gtk_window_util::DoCut(window_, panel_->GetWebContents()); |
| 891 } |
| 892 |
| 893 void PanelGtk::PanelCopy() { |
| 894 gtk_window_util::DoCopy(window_, panel_->GetWebContents()); |
| 895 } |
| 896 |
| 897 void PanelGtk::PanelPaste() { |
| 898 gtk_window_util::DoPaste(window_, panel_->GetWebContents()); |
| 899 } |
| 900 |
| 901 void PanelGtk::DrawAttention(bool draw_attention) { |
| 902 DCHECK((panel_->attention_mode() & Panel::USE_PANEL_ATTENTION) != 0); |
| 903 |
| 904 if (is_drawing_attention_ == draw_attention) |
| 905 return; |
| 906 |
| 907 is_drawing_attention_ = draw_attention; |
| 908 |
| 909 titlebar_->UpdateTextColor(); |
| 910 InvalidateWindow(); |
| 911 |
| 912 if ((panel_->attention_mode() & Panel::USE_SYSTEM_ATTENTION) != 0) { |
| 913 // May not be respected by all window managers. |
| 914 gtk_window_set_urgency_hint(window_, draw_attention); |
| 915 } |
| 916 } |
| 917 |
| 918 bool PanelGtk::IsDrawingAttention() const { |
| 919 return is_drawing_attention_; |
| 920 } |
| 921 |
| 922 bool PanelGtk::PreHandlePanelKeyboardEvent( |
| 923 const NativeWebKeyboardEvent& event, |
| 924 bool* is_keyboard_shortcut) { |
| 925 // No need to prehandle as no keys are reserved. |
| 926 return false; |
| 927 } |
| 928 |
| 929 void PanelGtk::HandlePanelKeyboardEvent( |
| 930 const NativeWebKeyboardEvent& event) { |
| 931 GdkEventKey* os_event = &event.os_event->key; |
| 932 if (os_event && event.type == WebKit::WebInputEvent::RawKeyDown) |
| 933 gtk_window_activate_key(window_, os_event); |
| 934 } |
| 935 |
| 936 void PanelGtk::FullScreenModeChanged(bool is_full_screen) { |
| 937 // Nothing to do here as z-order rules for panels ensures that they're below |
| 938 // any app running in full screen mode. |
| 939 } |
| 940 |
| 941 void PanelGtk::PanelExpansionStateChanging( |
| 942 Panel::ExpansionState old_state, Panel::ExpansionState new_state) { |
| 943 } |
| 944 |
| 945 void PanelGtk::AttachWebContents(content::WebContents* contents) { |
| 946 if (!contents) |
| 947 return; |
| 948 gfx::NativeView widget = contents->GetNativeView(); |
| 949 if (widget) { |
| 950 gtk_container_add(GTK_CONTAINER(contents_expanded_), widget); |
| 951 gtk_widget_show(widget); |
| 952 contents->WasShown(); |
| 953 } |
| 954 } |
| 955 |
| 956 void PanelGtk::DetachWebContents(content::WebContents* contents) { |
| 957 gfx::NativeView widget = contents->GetNativeView(); |
| 958 if (widget) { |
| 959 GtkWidget* parent = gtk_widget_get_parent(widget); |
| 960 if (parent) { |
| 961 DCHECK_EQ(parent, contents_expanded_); |
| 962 gtk_container_remove(GTK_CONTAINER(contents_expanded_), widget); |
| 963 } |
| 964 } |
| 965 } |
| 966 |
| 967 Browser* PanelGtk::GetPanelBrowser() const { |
| 968 return NULL; // legacy |
| 969 } |
| 970 |
| 971 gfx::Size PanelGtk::WindowSizeFromContentSize( |
| 972 const gfx::Size& content_size) const { |
| 973 gfx::Size& frame_size = GetFrameSize(); |
| 974 return gfx::Size(content_size.width() + frame_size.width(), |
| 975 content_size.height() + frame_size.height()); |
| 976 } |
| 977 |
| 978 gfx::Size PanelGtk::ContentSizeFromWindowSize( |
| 979 const gfx::Size& window_size) const { |
| 980 gfx::Size& frame_size = GetFrameSize(); |
| 981 return gfx::Size(window_size.width() - frame_size.width(), |
| 982 window_size.height() - frame_size.height()); |
| 983 } |
| 984 |
| 985 int PanelGtk::TitleOnlyHeight() const { |
| 986 GtkAllocation allocation; |
| 987 gtk_widget_get_allocation(titlebar_->widget(), &allocation); |
| 988 return allocation.height; |
| 989 } |
| 990 |
| 991 void PanelGtk::EnsurePanelFullyVisible() { |
| 992 gtk_window_present(window_); |
| 993 } |
| 994 |
| 995 void PanelGtk::SetPanelAlwaysOnTop(bool on_top) { |
| 996 gtk_window_set_keep_above(window_, on_top); |
| 997 } |
| 998 |
| 999 void PanelGtk::EnableResizeByMouse(bool enable) { |
| 1000 } |
| 1001 |
| 1002 void PanelGtk::UpdatePanelMinimizeRestoreButtonVisibility() { |
| 1003 titlebar_->UpdateMinimizeRestoreButtonVisibility(); |
| 1004 } |
| 1005 |
| 1006 void PanelGtk::StartBoundsAnimation( |
| 1007 const gfx::Rect& from_bounds, const gfx::Rect& to_bounds) { |
| 1008 animation_start_bounds_ = IsAnimatingBounds() ? |
| 1009 last_animation_progressed_bounds_ : from_bounds; |
| 1010 |
| 1011 bounds_animator_.reset(new PanelBoundsAnimation( |
| 1012 this, panel_.get(), animation_start_bounds_, to_bounds)); |
| 1013 |
| 1014 bounds_animator_->Start(); |
| 1015 last_animation_progressed_bounds_ = animation_start_bounds_; |
| 1016 } |
| 1017 |
| 1018 bool PanelGtk::IsAnimatingBounds() const { |
| 1019 return bounds_animator_.get() && bounds_animator_->is_animating(); |
| 1020 } |
| 1021 |
| 1022 void PanelGtk::AnimationEnded(const ui::Animation* animation) { |
| 1023 titlebar_->SendEnterNotifyToCloseButtonIfUnderMouse(); |
| 1024 panel_->manager()->OnPanelAnimationEnded(panel_.get()); |
| 1025 } |
| 1026 |
| 1027 void PanelGtk::AnimationProgressed(const ui::Animation* animation) { |
| 1028 DCHECK(is_shown_); |
| 1029 gfx::Rect new_bounds = bounds_animator_->CurrentValueBetween( |
| 1030 animation_start_bounds_, bounds_); |
| 1031 |
| 1032 gdk_window_move_resize(gtk_widget_get_window(GTK_WIDGET(window_)), |
| 1033 new_bounds.x(), new_bounds.y(), |
| 1034 new_bounds.width(), new_bounds.height()); |
| 1035 |
| 1036 last_animation_progressed_bounds_ = new_bounds; |
| 1037 } |
| 1038 |
| 1039 gfx::Size PanelGtk::GetNonClientFrameSize() const { |
| 1040 GtkAllocation window_allocation; |
| 1041 gtk_widget_get_allocation(window_container_, &window_allocation); |
| 1042 GtkAllocation contents_allocation; |
| 1043 gtk_widget_get_allocation(contents_expanded_, &contents_allocation); |
| 1044 return gfx::Size(window_allocation.width - contents_allocation.width, |
| 1045 window_allocation.height - contents_allocation.height); |
| 1046 } |
| 1047 |
| 1048 void PanelGtk::InvalidateWindow() { |
| 1049 GtkAllocation allocation; |
| 1050 gtk_widget_get_allocation(GTK_WIDGET(window_), &allocation); |
| 1051 gdk_window_invalidate_rect(gtk_widget_get_window(GTK_WIDGET(window_)), |
| 1052 &allocation, TRUE); |
| 1053 } |
| 1054 |
| 1055 // NativePanelTesting implementation. |
| 1056 class GtkNativePanelTesting : public NativePanelTesting { |
| 1057 public: |
| 1058 explicit GtkNativePanelTesting(PanelGtk* panel_gtk); |
| 1059 |
| 1060 private: |
| 1061 virtual void PressLeftMouseButtonTitlebar( |
| 1062 const gfx::Point& mouse_location, panel::ClickModifier modifier) OVERRIDE; |
| 1063 virtual void ReleaseMouseButtonTitlebar( |
| 1064 panel::ClickModifier modifier) OVERRIDE; |
| 1065 virtual void DragTitlebar(const gfx::Point& mouse_location) OVERRIDE; |
| 1066 virtual void CancelDragTitlebar() OVERRIDE; |
| 1067 virtual void FinishDragTitlebar() OVERRIDE; |
| 1068 virtual bool VerifyDrawingAttention() const OVERRIDE; |
| 1069 virtual bool VerifyActiveState(bool is_active) OVERRIDE; |
| 1070 virtual void WaitForWindowCreationToComplete() const OVERRIDE; |
| 1071 virtual bool IsWindowSizeKnown() const OVERRIDE; |
| 1072 virtual bool IsAnimatingBounds() const OVERRIDE; |
| 1073 virtual bool IsButtonVisible( |
| 1074 panel::TitlebarButtonType button_type) const OVERRIDE; |
| 1075 |
| 1076 PanelGtk* panel_gtk_; |
| 1077 }; |
| 1078 |
| 1079 NativePanelTesting* PanelGtk::CreateNativePanelTesting() { |
| 1080 return new GtkNativePanelTesting(this); |
| 1081 } |
| 1082 |
| 1083 GtkNativePanelTesting::GtkNativePanelTesting(PanelGtk* panel_gtk) |
| 1084 : panel_gtk_(panel_gtk) { |
| 1085 } |
| 1086 |
| 1087 void GtkNativePanelTesting::PressLeftMouseButtonTitlebar( |
| 1088 const gfx::Point& mouse_location, panel::ClickModifier modifier) { |
| 1089 // If there is an animation, wait for it to finish as we don't handle button |
| 1090 // clicks while animation is in progress. |
| 1091 while (panel_gtk_->IsAnimatingBounds()) |
| 1092 MessageLoopForUI::current()->RunAllPending(); |
| 1093 |
| 1094 GdkEvent* event = gdk_event_new(GDK_BUTTON_PRESS); |
| 1095 event->button.button = 1; |
| 1096 event->button.x_root = mouse_location.x(); |
| 1097 event->button.y_root = mouse_location.y(); |
| 1098 if (modifier == panel::APPLY_TO_ALL) |
| 1099 event->button.state |= GDK_CONTROL_MASK; |
| 1100 panel_gtk_->OnTitlebarButtonPressEvent( |
| 1101 NULL, reinterpret_cast<GdkEventButton*>(event)); |
| 1102 gdk_event_free(event); |
| 1103 MessageLoopForUI::current()->RunAllPending(); |
| 1104 } |
| 1105 |
| 1106 void GtkNativePanelTesting::ReleaseMouseButtonTitlebar( |
| 1107 panel::ClickModifier modifier) { |
| 1108 GdkEvent* event = gdk_event_new(GDK_BUTTON_RELEASE); |
| 1109 event->button.button = 1; |
| 1110 if (modifier == panel::APPLY_TO_ALL) |
| 1111 event->button.state |= GDK_CONTROL_MASK; |
| 1112 if (panel_gtk_->drag_helper_.get()) { |
| 1113 panel_gtk_->drag_helper_->OnButtonReleaseEvent( |
| 1114 NULL, reinterpret_cast<GdkEventButton*>(event)); |
| 1115 } else { |
| 1116 panel_gtk_->OnTitlebarButtonReleaseEvent( |
| 1117 NULL, reinterpret_cast<GdkEventButton*>(event)); |
| 1118 } |
| 1119 gdk_event_free(event); |
| 1120 MessageLoopForUI::current()->RunAllPending(); |
| 1121 } |
| 1122 |
| 1123 void GtkNativePanelTesting::DragTitlebar(const gfx::Point& mouse_location) { |
| 1124 if (!panel_gtk_->drag_helper_.get()) |
| 1125 return; |
| 1126 GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY); |
| 1127 event->motion.x_root = mouse_location.x(); |
| 1128 event->motion.y_root = mouse_location.y(); |
| 1129 panel_gtk_->drag_helper_->OnMouseMoveEvent( |
| 1130 NULL, reinterpret_cast<GdkEventMotion*>(event)); |
| 1131 gdk_event_free(event); |
| 1132 MessageLoopForUI::current()->RunAllPending(); |
| 1133 } |
| 1134 |
| 1135 void GtkNativePanelTesting::CancelDragTitlebar() { |
| 1136 if (!panel_gtk_->drag_helper_.get()) |
| 1137 return; |
| 1138 panel_gtk_->drag_helper_->OnGrabBrokenEvent(NULL, NULL); |
| 1139 MessageLoopForUI::current()->RunAllPending(); |
| 1140 } |
| 1141 |
| 1142 void GtkNativePanelTesting::FinishDragTitlebar() { |
| 1143 if (!panel_gtk_->drag_helper_.get()) |
| 1144 return; |
| 1145 ReleaseMouseButtonTitlebar(panel::NO_MODIFIER); |
| 1146 } |
| 1147 |
| 1148 bool GtkNativePanelTesting::VerifyDrawingAttention() const { |
| 1149 return panel_gtk_->IsDrawingAttention(); |
| 1150 } |
| 1151 |
| 1152 bool GtkNativePanelTesting::VerifyActiveState(bool is_active) { |
| 1153 // TODO(jianli): to be implemented. http://crbug.com/102737 |
| 1154 return false; |
| 1155 } |
| 1156 |
| 1157 void GtkNativePanelTesting::WaitForWindowCreationToComplete() const { |
| 1158 while (GetFrameSize().IsEmpty()) |
| 1159 MessageLoopForUI::current()->RunAllPending(); |
| 1160 while (panel_gtk_->IsAnimatingBounds()) |
| 1161 MessageLoopForUI::current()->RunAllPending(); |
| 1162 } |
| 1163 |
| 1164 bool GtkNativePanelTesting::IsWindowSizeKnown() const { |
| 1165 return !GetFrameSize().IsEmpty(); |
| 1166 } |
| 1167 |
| 1168 bool GtkNativePanelTesting::IsAnimatingBounds() const { |
| 1169 return panel_gtk_->IsAnimatingBounds(); |
| 1170 } |
| 1171 |
| 1172 bool GtkNativePanelTesting::IsButtonVisible( |
| 1173 panel::TitlebarButtonType button_type) const { |
| 1174 PanelTitlebarGtk* titlebar = panel_gtk_->titlebar(); |
| 1175 CustomDrawButton* button; |
| 1176 switch (button_type) { |
| 1177 case panel::CLOSE_BUTTON: |
| 1178 button = titlebar->close_button(); |
| 1179 break; |
| 1180 case panel::MINIMIZE_BUTTON: |
| 1181 button = titlebar->minimize_button(); |
| 1182 break; |
| 1183 case panel::RESTORE_BUTTON: |
| 1184 button = titlebar->restore_button(); |
| 1185 break; |
| 1186 default: |
| 1187 NOTREACHED(); |
| 1188 return false; |
| 1189 } |
| 1190 return gtk_widget_get_visible(button->widget()); |
| 1191 } |
OLD | NEW |