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