Home | History | Annotate | Download | only in panels
      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