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