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_expanded_container.h"
37#include "ui/base/gtk/gtk_hig_constants.h"
38#include "ui/base/x/active_window_watcher_x.h"
39#include "ui/base/x/x11_util.h"
40#include "ui/gfx/canvas.h"
41#include "ui/gfx/gtk_compat.h"
42#include "ui/gfx/image/cairo_cached_surface.h"
43#include "ui/gfx/image/image.h"
44
45using content::NativeWebKeyboardEvent;
46using content::WebContents;
47
48namespace {
49
50const char* kPanelWindowKey = "__PANEL_GTK__";
51
52// The number of milliseconds between loading animation frames.
53const 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.
57const 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.)
63const 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.
66const int kResizeAreaCornerSize = 16;
67
68// Colors used to draw frame background under default theme.
69const SkColor kActiveBackgroundDefaultColor = SkColorSetRGB(0x3a, 0x3d, 0x3d);
70const SkColor kInactiveBackgroundDefaultColor = SkColorSetRGB(0x7a, 0x7c, 0x7c);
71const SkColor kAttentionBackgroundDefaultColor =
72    SkColorSetRGB(0x53, 0xa9, 0x3f);
73const SkColor kMinimizeBackgroundDefaultColor = SkColorSetRGB(0xf5, 0xf4, 0xf0);
74const SkColor kMinimizeBorderDefaultColor = SkColorSetRGB(0xc9, 0xc9, 0xc9);
75
76// Set minimium width for window really small.
77const int kMinWindowWidth = 26;
78
79// Table of supported accelerators in Panels.
80const 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.
131typedef std::map<ui::Accelerator, int> AcceleratorMap;
132
133const 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
146gfx::Image CreateImageForColor(SkColor color) {
147  gfx::Canvas canvas(gfx::Size(1, 1), 1.0f, true);
148  canvas.DrawColor(color);
149  return gfx::Image(gfx::ImageSkia(canvas.ExtractImageRep()));
150}
151
152const gfx::Image GetActiveBackgroundDefaultImage() {
153  CR_DEFINE_STATIC_LOCAL(gfx::Image, image, ());
154  if (image.IsEmpty())
155    image = CreateImageForColor(kActiveBackgroundDefaultColor);
156  return image;
157}
158
159gfx::Image GetInactiveBackgroundDefaultImage() {
160  CR_DEFINE_STATIC_LOCAL(gfx::Image, image, ());
161  if (image.IsEmpty())
162    image = CreateImageForColor(kInactiveBackgroundDefaultColor);
163  return image;
164}
165
166gfx::Image GetAttentionBackgroundDefaultImage() {
167  CR_DEFINE_STATIC_LOCAL(gfx::Image, image, ());
168  if (image.IsEmpty())
169    image = CreateImageForColor(kAttentionBackgroundDefaultColor);
170  return image;
171}
172
173gfx::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.
182GQuark 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.
190gfx::Size& GetFrameSize() {
191  CR_DEFINE_STATIC_LOCAL(gfx::Size, frame_size, ());
192  return frame_size;
193}
194
195void 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
203NativePanel* 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
211PanelGtk::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
230PanelGtk::~PanelGtk() {
231  ui::ActiveWindowWatcherX::RemoveObserver(this);
232}
233
234void 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
324void PanelGtk::SetWindowCornerStyle(panel::CornerStyle corner_style) {
325  corner_style_ = corner_style;
326  UpdateWindowShape();
327}
328
329void PanelGtk::MinimizePanelBySystem() {
330  gtk_window_iconify(window_);
331}
332
333bool PanelGtk::IsPanelMinimizedBySystem() const {
334  return is_minimized_;
335}
336
337bool 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
357void PanelGtk::ShowShadow(bool show) {
358  // Shadow is not supported for GTK panel.
359}
360
361void 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
396gboolean 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
425gboolean PanelGtk::OnWindowState(GtkWidget* widget,
426                                 GdkEventWindowState* event) {
427  is_minimized_ = event->new_window_state & GDK_WINDOW_STATE_ICONIFIED;
428  return FALSE;
429}
430
431void 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
448void 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
465gboolean 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
477gboolean 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
493bool 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
547gfx::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
563gboolean 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
617void 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
626gboolean 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
662gboolean 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
672gboolean 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
702gboolean 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
735void 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.
761gboolean 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
770void 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
781void PanelGtk::ShowPanel() {
782  gtk_window_present(window_);
783  RevealPanel();
784}
785
786void PanelGtk::ShowPanelInactive() {
787  gtk_window_set_focus_on_map(window_, false);
788  gtk_widget_show(GTK_WIDGET(window_));
789  RevealPanel();
790}
791
792void PanelGtk::RevealPanel() {
793  DCHECK(!is_shown_);
794  is_shown_ = true;
795  SetBoundsInternal(bounds_);
796}
797
798gfx::Rect PanelGtk::GetPanelBounds() const {
799  return bounds_;
800}
801
802void PanelGtk::SetPanelBounds(const gfx::Rect& bounds) {
803  SetBoundsInternal(bounds);
804}
805
806void PanelGtk::SetPanelBoundsInstantly(const gfx::Rect& bounds) {
807  SetBoundsInternal(bounds);
808}
809
810void 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
823void 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
861void 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
873void 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
907bool PanelGtk::IsPanelActive() const {
908  return is_active_;
909}
910
911void PanelGtk::PreventActivationByOS(bool prevent_activation) {
912  gtk_window_set_accept_focus(window_, !prevent_activation);
913}
914
915gfx::NativeWindow PanelGtk::GetNativePanelWindow() {
916  return window_;
917}
918
919void PanelGtk::UpdatePanelTitleBar() {
920  TRACE_EVENT0("ui::gtk", "PanelGtk::UpdatePanelTitleBar");
921  base::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
930void 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
948void PanelGtk::LoadingAnimationCallback() {
949  titlebar_->UpdateThrobber(panel_->GetWebContents());
950}
951
952void PanelGtk::PanelWebContentsFocused(content::WebContents* contents) {
953  // Nothing to do.
954}
955
956void PanelGtk::PanelCut() {
957  gtk_window_util::DoCut(window_, panel_->GetWebContents());
958}
959
960void PanelGtk::PanelCopy() {
961  gtk_window_util::DoCopy(window_, panel_->GetWebContents());
962}
963
964void PanelGtk::PanelPaste() {
965  gtk_window_util::DoPaste(window_, panel_->GetWebContents());
966}
967
968void 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
985bool PanelGtk::IsDrawingAttention() const {
986  return is_drawing_attention_;
987}
988
989void PanelGtk::HandlePanelKeyboardEvent(
990    const NativeWebKeyboardEvent& event) {
991  GdkEventKey* os_event = &event.os_event->key;
992  if (os_event && event.type == blink::WebInputEvent::RawKeyDown)
993    gtk_window_activate_key(window_, os_event);
994}
995
996void PanelGtk::FullScreenModeChanged(bool is_full_screen) {
997  // No need to hide panels when entering the full-screen mode because the
998  // full-screen window will automatically be placed above all other windows.
999  if (is_full_screen)
1000    return;
1001
1002  // Show the panel if not yet when leaving the full-screen mode. This is
1003  // because the panel is not shown when it is being created under full-screen
1004  // mode.
1005  GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_));
1006  if (!GDK_IS_WINDOW(gdk_window) || !gdk_window_is_visible(gdk_window))
1007    ShowPanelInactive();
1008}
1009
1010void PanelGtk::PanelExpansionStateChanging(
1011    Panel::ExpansionState old_state, Panel::ExpansionState new_state) {
1012}
1013
1014void PanelGtk::AttachWebContents(content::WebContents* contents) {
1015  if (!contents)
1016    return;
1017  gfx::NativeView widget = contents->GetView()->GetNativeView();
1018  if (widget) {
1019    gtk_container_add(GTK_CONTAINER(contents_expanded_), widget);
1020    gtk_widget_show(widget);
1021    contents->WasShown();
1022  }
1023}
1024
1025void PanelGtk::DetachWebContents(content::WebContents* contents) {
1026  gfx::NativeView widget = contents->GetView()->GetNativeView();
1027  if (widget) {
1028    GtkWidget* parent = gtk_widget_get_parent(widget);
1029    if (parent) {
1030      DCHECK_EQ(parent, contents_expanded_);
1031      gtk_container_remove(GTK_CONTAINER(contents_expanded_), widget);
1032    }
1033  }
1034}
1035
1036gfx::Size PanelGtk::WindowSizeFromContentSize(
1037    const gfx::Size& content_size) const {
1038  gfx::Size& frame_size = GetFrameSize();
1039  return gfx::Size(content_size.width() + frame_size.width(),
1040                   content_size.height() + frame_size.height());
1041}
1042
1043gfx::Size PanelGtk::ContentSizeFromWindowSize(
1044    const gfx::Size& window_size) const {
1045  gfx::Size& frame_size = GetFrameSize();
1046  return gfx::Size(window_size.width() - frame_size.width(),
1047                   window_size.height() - frame_size.height());
1048}
1049
1050int PanelGtk::TitleOnlyHeight() const {
1051  gfx::Size& frame_size = GetFrameSize();
1052  if (!frame_size.IsEmpty())
1053    return panel::kTitlebarHeight;
1054
1055  NOTREACHED() << "Checking title height before window allocated";
1056  return 0;
1057}
1058
1059bool PanelGtk::IsPanelAlwaysOnTop() const {
1060  return always_on_top_;
1061}
1062
1063void PanelGtk::SetPanelAlwaysOnTop(bool on_top) {
1064  always_on_top_ = on_top;
1065
1066  gtk_window_set_keep_above(window_, on_top);
1067
1068  // Do not show an icon in the task bar for always-on-top windows.
1069  // Window operations such as close, minimize etc. can only be done
1070  // from the panel UI.
1071  gtk_window_set_skip_taskbar_hint(window_, on_top);
1072
1073  // Show always-on-top windows on all the virtual desktops.
1074  if (on_top)
1075    gtk_window_stick(window_);
1076  else
1077    gtk_window_unstick(window_);
1078}
1079
1080void PanelGtk::UpdatePanelMinimizeRestoreButtonVisibility() {
1081  titlebar_->UpdateMinimizeRestoreButtonVisibility();
1082}
1083
1084gfx::Size PanelGtk::GetNonClientFrameSize() const {
1085  GtkAllocation window_allocation;
1086  gtk_widget_get_allocation(window_container_, &window_allocation);
1087  GtkAllocation contents_allocation;
1088  gtk_widget_get_allocation(contents_expanded_, &contents_allocation);
1089  return gfx::Size(window_allocation.width - contents_allocation.width,
1090                   window_allocation.height - contents_allocation.height);
1091}
1092
1093void PanelGtk::InvalidateWindow() {
1094  GtkAllocation allocation;
1095  gtk_widget_get_allocation(GTK_WIDGET(window_), &allocation);
1096  gdk_window_invalidate_rect(gtk_widget_get_window(GTK_WIDGET(window_)),
1097                             &allocation, TRUE);
1098}
1099
1100// NativePanelTesting implementation.
1101class GtkNativePanelTesting : public NativePanelTesting {
1102 public:
1103  explicit GtkNativePanelTesting(PanelGtk* panel_gtk);
1104
1105 private:
1106  virtual void PressLeftMouseButtonTitlebar(
1107      const gfx::Point& mouse_location, panel::ClickModifier modifier) OVERRIDE;
1108  virtual void ReleaseMouseButtonTitlebar(
1109      panel::ClickModifier modifier) OVERRIDE;
1110  virtual void DragTitlebar(const gfx::Point& mouse_location) OVERRIDE;
1111  virtual void CancelDragTitlebar() OVERRIDE;
1112  virtual void FinishDragTitlebar() OVERRIDE;
1113  virtual bool VerifyDrawingAttention() const OVERRIDE;
1114  virtual bool VerifyActiveState(bool is_active) OVERRIDE;
1115  virtual bool VerifyAppIcon() const OVERRIDE;
1116  virtual bool VerifySystemMinimizeState() const OVERRIDE;
1117  virtual bool IsWindowVisible() const OVERRIDE;
1118  virtual bool IsWindowSizeKnown() const OVERRIDE;
1119  virtual bool IsAnimatingBounds() const OVERRIDE;
1120  virtual bool IsButtonVisible(
1121      panel::TitlebarButtonType button_type) const OVERRIDE;
1122  virtual panel::CornerStyle GetWindowCornerStyle() const OVERRIDE;
1123  virtual bool EnsureApplicationRunOnForeground() OVERRIDE;
1124
1125  PanelGtk* panel_gtk_;
1126};
1127
1128NativePanelTesting* PanelGtk::CreateNativePanelTesting() {
1129  return new GtkNativePanelTesting(this);
1130}
1131
1132GtkNativePanelTesting::GtkNativePanelTesting(PanelGtk* panel_gtk)
1133    : panel_gtk_(panel_gtk) {
1134}
1135
1136void GtkNativePanelTesting::PressLeftMouseButtonTitlebar(
1137    const gfx::Point& mouse_location, panel::ClickModifier modifier) {
1138
1139  GdkEvent* event = gdk_event_new(GDK_BUTTON_PRESS);
1140  event->button.button = 1;
1141  event->button.x_root = mouse_location.x();
1142  event->button.y_root = mouse_location.y();
1143  if (modifier == panel::APPLY_TO_ALL)
1144    event->button.state |= GDK_CONTROL_MASK;
1145  panel_gtk_->OnTitlebarButtonPressEvent(
1146      NULL, reinterpret_cast<GdkEventButton*>(event));
1147  gdk_event_free(event);
1148  base::MessageLoopForUI::current()->RunUntilIdle();
1149}
1150
1151void GtkNativePanelTesting::ReleaseMouseButtonTitlebar(
1152    panel::ClickModifier modifier) {
1153  GdkEvent* event = gdk_event_new(GDK_BUTTON_RELEASE);
1154  event->button.button = 1;
1155  if (modifier == panel::APPLY_TO_ALL)
1156    event->button.state |= GDK_CONTROL_MASK;
1157  if (panel_gtk_->drag_helper_.get()) {
1158    panel_gtk_->drag_helper_->OnButtonReleaseEvent(
1159        NULL, reinterpret_cast<GdkEventButton*>(event));
1160  } else {
1161    panel_gtk_->OnTitlebarButtonReleaseEvent(
1162        NULL, reinterpret_cast<GdkEventButton*>(event));
1163  }
1164  gdk_event_free(event);
1165  base::MessageLoopForUI::current()->RunUntilIdle();
1166}
1167
1168void GtkNativePanelTesting::DragTitlebar(const gfx::Point& mouse_location) {
1169  if (!panel_gtk_->drag_helper_.get())
1170    return;
1171  GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY);
1172  event->motion.x_root = mouse_location.x();
1173  event->motion.y_root = mouse_location.y();
1174  panel_gtk_->drag_helper_->OnMouseMoveEvent(
1175      NULL, reinterpret_cast<GdkEventMotion*>(event));
1176  gdk_event_free(event);
1177  base::MessageLoopForUI::current()->RunUntilIdle();
1178}
1179
1180void GtkNativePanelTesting::CancelDragTitlebar() {
1181  if (!panel_gtk_->drag_helper_.get())
1182    return;
1183  panel_gtk_->drag_helper_->OnGrabBrokenEvent(NULL, NULL);
1184  base::MessageLoopForUI::current()->RunUntilIdle();
1185}
1186
1187void GtkNativePanelTesting::FinishDragTitlebar() {
1188  if (!panel_gtk_->drag_helper_.get())
1189    return;
1190  ReleaseMouseButtonTitlebar(panel::NO_MODIFIER);
1191}
1192
1193bool GtkNativePanelTesting::VerifyDrawingAttention() const {
1194  return panel_gtk_->IsDrawingAttention();
1195}
1196
1197bool GtkNativePanelTesting::VerifyActiveState(bool is_active) {
1198  return gtk_window_is_active(panel_gtk_->GetNativePanelWindow()) == is_active;
1199}
1200
1201bool GtkNativePanelTesting::VerifyAppIcon() const {
1202  GdkPixbuf* icon = gtk_window_get_icon(panel_gtk_->GetNativePanelWindow());
1203  return icon &&
1204         gdk_pixbuf_get_width(icon) == panel::kPanelAppIconSize &&
1205         gdk_pixbuf_get_height(icon) == panel::kPanelAppIconSize;
1206}
1207
1208bool GtkNativePanelTesting::VerifySystemMinimizeState() const {
1209  // TODO(jianli): to be implemented.
1210  return true;
1211}
1212
1213bool GtkNativePanelTesting::IsWindowVisible() const {
1214  GdkWindow* gdk_window =
1215      gtk_widget_get_window(GTK_WIDGET(panel_gtk_->GetNativePanelWindow()));
1216  return GDK_IS_WINDOW(gdk_window) && gdk_window_is_visible(gdk_window);
1217}
1218
1219bool GtkNativePanelTesting::IsWindowSizeKnown() const {
1220  return !GetFrameSize().IsEmpty();
1221}
1222
1223bool GtkNativePanelTesting::IsAnimatingBounds() const {
1224  return false;
1225}
1226
1227bool GtkNativePanelTesting::IsButtonVisible(
1228    panel::TitlebarButtonType button_type) const {
1229  PanelTitlebarGtk* titlebar = panel_gtk_->titlebar();
1230  CustomDrawButton* button;
1231  switch (button_type) {
1232    case panel::CLOSE_BUTTON:
1233      button = titlebar->close_button();
1234      break;
1235    case panel::MINIMIZE_BUTTON:
1236      button = titlebar->minimize_button();
1237      break;
1238    case panel::RESTORE_BUTTON:
1239      button = titlebar->restore_button();
1240      break;
1241    default:
1242      NOTREACHED();
1243      return false;
1244  }
1245  return gtk_widget_get_visible(button->widget());
1246}
1247
1248panel::CornerStyle GtkNativePanelTesting::GetWindowCornerStyle() const {
1249  return panel_gtk_->corner_style_;
1250}
1251
1252bool GtkNativePanelTesting::EnsureApplicationRunOnForeground() {
1253  // Not needed on GTK.
1254  return true;
1255}
1256