• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/ui/gtk/panels/panel_gtk.h"
6 
7 #include <gdk/gdk.h>
8 #include <gdk/gdkkeysyms.h>
9 #include <X11/XF86keysym.h>
10 
11 #include "base/bind.h"
12 #include "base/debug/trace_event.h"
13 #include "base/logging.h"
14 #include "base/message_loop/message_loop.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "chrome/app/chrome_command_ids.h"
17 #include "chrome/browser/chrome_notification_types.h"
18 #include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog_queue.h"
19 #include "chrome/browser/ui/gtk/custom_button.h"
20 #include "chrome/browser/ui/gtk/gtk_util.h"
21 #include "chrome/browser/ui/gtk/gtk_window_util.h"
22 #include "chrome/browser/ui/gtk/panels/panel_drag_gtk.h"
23 #include "chrome/browser/ui/gtk/panels/panel_titlebar_gtk.h"
24 #include "chrome/browser/ui/panels/display_settings_provider.h"
25 #include "chrome/browser/ui/panels/panel.h"
26 #include "chrome/browser/ui/panels/panel_constants.h"
27 #include "chrome/browser/ui/panels/panel_manager.h"
28 #include "chrome/browser/ui/panels/stacked_panel_collection.h"
29 #include "chrome/browser/web_applications/web_app.h"
30 #include "content/public/browser/native_web_keyboard_event.h"
31 #include "content/public/browser/notification_service.h"
32 #include "content/public/browser/web_contents.h"
33 #include "content/public/browser/web_contents_view.h"
34 #include "grit/ui_resources.h"
35 #include "ui/base/accelerators/platform_accelerator_gtk.h"
36 #include "ui/base/gtk/gtk_expanded_container.h"
37 #include "ui/base/gtk/gtk_hig_constants.h"
38 #include "ui/base/x/active_window_watcher_x.h"
39 #include "ui/base/x/x11_util.h"
40 #include "ui/gfx/canvas.h"
41 #include "ui/gfx/gtk_compat.h"
42 #include "ui/gfx/image/cairo_cached_surface.h"
43 #include "ui/gfx/image/image.h"
44 
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 
GetAcceleratorTable()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 
CreateImageForColor(SkColor color)146 gfx::Image CreateImageForColor(SkColor color) {
147   gfx::Canvas canvas(gfx::Size(1, 1), 1.0f, true);
148   canvas.DrawColor(color);
149   return gfx::Image(gfx::ImageSkia(canvas.ExtractImageRep()));
150 }
151 
GetActiveBackgroundDefaultImage()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 
GetInactiveBackgroundDefaultImage()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 
GetAttentionBackgroundDefaultImage()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 
GetMinimizeBackgroundDefaultImage()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.
GetPanelWindowQuarkKey()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.
GetFrameSize()190 gfx::Size& GetFrameSize() {
191   CR_DEFINE_STATIC_LOCAL(gfx::Size, frame_size, ());
192   return frame_size;
193 }
194 
SetFrameSize(const gfx::Size & new_size)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
CreateNativePanel(Panel * panel,const gfx::Rect & bounds,bool always_on_top)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 
PanelGtk(Panel * panel,const gfx::Rect & bounds,bool always_on_top)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 
~PanelGtk()230 PanelGtk::~PanelGtk() {
231   ui::ActiveWindowWatcherX::RemoveObserver(this);
232 }
233 
Init()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 
SetWindowCornerStyle(panel::CornerStyle corner_style)324 void PanelGtk::SetWindowCornerStyle(panel::CornerStyle corner_style) {
325   corner_style_ = corner_style;
326   UpdateWindowShape();
327 }
328 
MinimizePanelBySystem()329 void PanelGtk::MinimizePanelBySystem() {
330   gtk_window_iconify(window_);
331 }
332 
IsPanelMinimizedBySystem() const333 bool PanelGtk::IsPanelMinimizedBySystem() const {
334   return is_minimized_;
335 }
336 
IsPanelShownOnActiveDesktop() const337 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 
ShowShadow(bool show)357 void PanelGtk::ShowShadow(bool show) {
358   // Shadow is not supported for GTK panel.
359 }
360 
UpdateWindowShape()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 
OnConfigure(GtkWidget * widget,GdkEventConfigure * event)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 
OnWindowState(GtkWidget * widget,GdkEventWindowState * event)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 
ConnectAccelerators()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 
DisconnectAccelerators()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
OnGtkAccelerator(GtkAccelGroup * accel_group,GObject * acceleratable,guint keyval,GdkModifierType modifier,void * user_data)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 
OnKeyPress(GtkWidget * widget,GdkEventKey * event)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 
GetWindowEdge(int x,int y,GdkWindowEdge * edge) const493 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 
GetFrameBackground() const547 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 
OnCustomFrameExpose(GtkWidget * widget,GdkEventExpose * event)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 
EnsureDragHelperCreated()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 
OnTitlebarButtonPressEvent(GtkWidget * widget,GdkEventButton * event)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 
OnTitlebarButtonReleaseEvent(GtkWidget * widget,GdkEventButton * event)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 
OnMouseMoveEvent(GtkWidget * widget,GdkEventMotion * event)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 
OnButtonPressEvent(GtkWidget * widget,GdkEventButton * event)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 
ActiveWindowChanged(GdkWindow * active_window)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.
OnMainWindowDeleteEvent(GtkWidget * widget,GdkEvent * event)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 
OnMainWindowDestroy(GtkWidget * widget)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 
ShowPanel()781 void PanelGtk::ShowPanel() {
782   gtk_window_present(window_);
783   RevealPanel();
784 }
785 
ShowPanelInactive()786 void PanelGtk::ShowPanelInactive() {
787   gtk_window_set_focus_on_map(window_, false);
788   gtk_widget_show(GTK_WIDGET(window_));
789   RevealPanel();
790 }
791 
RevealPanel()792 void PanelGtk::RevealPanel() {
793   DCHECK(!is_shown_);
794   is_shown_ = true;
795   SetBoundsInternal(bounds_);
796 }
797 
GetPanelBounds() const798 gfx::Rect PanelGtk::GetPanelBounds() const {
799   return bounds_;
800 }
801 
SetPanelBounds(const gfx::Rect & bounds)802 void PanelGtk::SetPanelBounds(const gfx::Rect& bounds) {
803   SetBoundsInternal(bounds);
804 }
805 
SetPanelBoundsInstantly(const gfx::Rect & bounds)806 void PanelGtk::SetPanelBoundsInstantly(const gfx::Rect& bounds) {
807   SetBoundsInternal(bounds);
808 }
809 
SetBoundsInternal(const gfx::Rect & bounds)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 
ClosePanel()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 
ActivatePanel()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 
DeactivatePanel()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 
IsPanelActive() const907 bool PanelGtk::IsPanelActive() const {
908   return is_active_;
909 }
910 
PreventActivationByOS(bool prevent_activation)911 void PanelGtk::PreventActivationByOS(bool prevent_activation) {
912   gtk_window_set_accept_focus(window_, !prevent_activation);
913 }
914 
GetNativePanelWindow()915 gfx::NativeWindow PanelGtk::GetNativePanelWindow() {
916   return window_;
917 }
918 
UpdatePanelTitleBar()919 void PanelGtk::UpdatePanelTitleBar() {
920   TRACE_EVENT0("ui::gtk", "PanelGtk::UpdatePanelTitleBar");
921   base::string16 title = panel_->GetWindowTitle();
922   gtk_window_set_title(window_, UTF16ToUTF8(title).c_str());
923   titlebar_->UpdateTitleAndIcon();
924 
925   gfx::Image app_icon = panel_->app_icon();
926   if (!app_icon.IsEmpty())
927     gtk_util::SetWindowIcon(window_, panel_->profile(), app_icon.ToGdkPixbuf());
928 }
929 
UpdatePanelLoadingAnimations(bool should_animate)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 
LoadingAnimationCallback()948 void PanelGtk::LoadingAnimationCallback() {
949   titlebar_->UpdateThrobber(panel_->GetWebContents());
950 }
951 
PanelWebContentsFocused(content::WebContents * contents)952 void PanelGtk::PanelWebContentsFocused(content::WebContents* contents) {
953   // Nothing to do.
954 }
955 
PanelCut()956 void PanelGtk::PanelCut() {
957   gtk_window_util::DoCut(window_, panel_->GetWebContents());
958 }
959 
PanelCopy()960 void PanelGtk::PanelCopy() {
961   gtk_window_util::DoCopy(window_, panel_->GetWebContents());
962 }
963 
PanelPaste()964 void PanelGtk::PanelPaste() {
965   gtk_window_util::DoPaste(window_, panel_->GetWebContents());
966 }
967 
DrawAttention(bool draw_attention)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 
IsDrawingAttention() const985 bool PanelGtk::IsDrawingAttention() const {
986   return is_drawing_attention_;
987 }
988 
HandlePanelKeyboardEvent(const NativeWebKeyboardEvent & event)989 void PanelGtk::HandlePanelKeyboardEvent(
990     const NativeWebKeyboardEvent& event) {
991   GdkEventKey* os_event = &event.os_event->key;
992   if (os_event && event.type == blink::WebInputEvent::RawKeyDown)
993     gtk_window_activate_key(window_, os_event);
994 }
995 
FullScreenModeChanged(bool is_full_screen)996 void PanelGtk::FullScreenModeChanged(bool is_full_screen) {
997   // No need to hide panels when entering the full-screen mode because the
998   // full-screen window will automatically be placed above all other windows.
999   if (is_full_screen)
1000     return;
1001 
1002   // Show the panel if not yet when leaving the full-screen mode. This is
1003   // because the panel is not shown when it is being created under full-screen
1004   // mode.
1005   GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_));
1006   if (!GDK_IS_WINDOW(gdk_window) || !gdk_window_is_visible(gdk_window))
1007     ShowPanelInactive();
1008 }
1009 
PanelExpansionStateChanging(Panel::ExpansionState old_state,Panel::ExpansionState new_state)1010 void PanelGtk::PanelExpansionStateChanging(
1011     Panel::ExpansionState old_state, Panel::ExpansionState new_state) {
1012 }
1013 
AttachWebContents(content::WebContents * contents)1014 void PanelGtk::AttachWebContents(content::WebContents* contents) {
1015   if (!contents)
1016     return;
1017   gfx::NativeView widget = contents->GetView()->GetNativeView();
1018   if (widget) {
1019     gtk_container_add(GTK_CONTAINER(contents_expanded_), widget);
1020     gtk_widget_show(widget);
1021     contents->WasShown();
1022   }
1023 }
1024 
DetachWebContents(content::WebContents * contents)1025 void PanelGtk::DetachWebContents(content::WebContents* contents) {
1026   gfx::NativeView widget = contents->GetView()->GetNativeView();
1027   if (widget) {
1028     GtkWidget* parent = gtk_widget_get_parent(widget);
1029     if (parent) {
1030       DCHECK_EQ(parent, contents_expanded_);
1031       gtk_container_remove(GTK_CONTAINER(contents_expanded_), widget);
1032     }
1033   }
1034 }
1035 
WindowSizeFromContentSize(const gfx::Size & content_size) const1036 gfx::Size PanelGtk::WindowSizeFromContentSize(
1037     const gfx::Size& content_size) const {
1038   gfx::Size& frame_size = GetFrameSize();
1039   return gfx::Size(content_size.width() + frame_size.width(),
1040                    content_size.height() + frame_size.height());
1041 }
1042 
ContentSizeFromWindowSize(const gfx::Size & window_size) const1043 gfx::Size PanelGtk::ContentSizeFromWindowSize(
1044     const gfx::Size& window_size) const {
1045   gfx::Size& frame_size = GetFrameSize();
1046   return gfx::Size(window_size.width() - frame_size.width(),
1047                    window_size.height() - frame_size.height());
1048 }
1049 
TitleOnlyHeight() const1050 int PanelGtk::TitleOnlyHeight() const {
1051   gfx::Size& frame_size = GetFrameSize();
1052   if (!frame_size.IsEmpty())
1053     return panel::kTitlebarHeight;
1054 
1055   NOTREACHED() << "Checking title height before window allocated";
1056   return 0;
1057 }
1058 
IsPanelAlwaysOnTop() const1059 bool PanelGtk::IsPanelAlwaysOnTop() const {
1060   return always_on_top_;
1061 }
1062 
SetPanelAlwaysOnTop(bool on_top)1063 void PanelGtk::SetPanelAlwaysOnTop(bool on_top) {
1064   always_on_top_ = on_top;
1065 
1066   gtk_window_set_keep_above(window_, on_top);
1067 
1068   // Do not show an icon in the task bar for always-on-top windows.
1069   // Window operations such as close, minimize etc. can only be done
1070   // from the panel UI.
1071   gtk_window_set_skip_taskbar_hint(window_, on_top);
1072 
1073   // Show always-on-top windows on all the virtual desktops.
1074   if (on_top)
1075     gtk_window_stick(window_);
1076   else
1077     gtk_window_unstick(window_);
1078 }
1079 
UpdatePanelMinimizeRestoreButtonVisibility()1080 void PanelGtk::UpdatePanelMinimizeRestoreButtonVisibility() {
1081   titlebar_->UpdateMinimizeRestoreButtonVisibility();
1082 }
1083 
GetNonClientFrameSize() const1084 gfx::Size PanelGtk::GetNonClientFrameSize() const {
1085   GtkAllocation window_allocation;
1086   gtk_widget_get_allocation(window_container_, &window_allocation);
1087   GtkAllocation contents_allocation;
1088   gtk_widget_get_allocation(contents_expanded_, &contents_allocation);
1089   return gfx::Size(window_allocation.width - contents_allocation.width,
1090                    window_allocation.height - contents_allocation.height);
1091 }
1092 
InvalidateWindow()1093 void PanelGtk::InvalidateWindow() {
1094   GtkAllocation allocation;
1095   gtk_widget_get_allocation(GTK_WIDGET(window_), &allocation);
1096   gdk_window_invalidate_rect(gtk_widget_get_window(GTK_WIDGET(window_)),
1097                              &allocation, TRUE);
1098 }
1099 
1100 // NativePanelTesting implementation.
1101 class GtkNativePanelTesting : public NativePanelTesting {
1102  public:
1103   explicit GtkNativePanelTesting(PanelGtk* panel_gtk);
1104 
1105  private:
1106   virtual void PressLeftMouseButtonTitlebar(
1107       const gfx::Point& mouse_location, panel::ClickModifier modifier) OVERRIDE;
1108   virtual void ReleaseMouseButtonTitlebar(
1109       panel::ClickModifier modifier) OVERRIDE;
1110   virtual void DragTitlebar(const gfx::Point& mouse_location) OVERRIDE;
1111   virtual void CancelDragTitlebar() OVERRIDE;
1112   virtual void FinishDragTitlebar() OVERRIDE;
1113   virtual bool VerifyDrawingAttention() const OVERRIDE;
1114   virtual bool VerifyActiveState(bool is_active) OVERRIDE;
1115   virtual bool VerifyAppIcon() const OVERRIDE;
1116   virtual bool VerifySystemMinimizeState() const OVERRIDE;
1117   virtual bool IsWindowVisible() const OVERRIDE;
1118   virtual bool IsWindowSizeKnown() const OVERRIDE;
1119   virtual bool IsAnimatingBounds() const OVERRIDE;
1120   virtual bool IsButtonVisible(
1121       panel::TitlebarButtonType button_type) const OVERRIDE;
1122   virtual panel::CornerStyle GetWindowCornerStyle() const OVERRIDE;
1123   virtual bool EnsureApplicationRunOnForeground() OVERRIDE;
1124 
1125   PanelGtk* panel_gtk_;
1126 };
1127 
CreateNativePanelTesting()1128 NativePanelTesting* PanelGtk::CreateNativePanelTesting() {
1129   return new GtkNativePanelTesting(this);
1130 }
1131 
GtkNativePanelTesting(PanelGtk * panel_gtk)1132 GtkNativePanelTesting::GtkNativePanelTesting(PanelGtk* panel_gtk)
1133     : panel_gtk_(panel_gtk) {
1134 }
1135 
PressLeftMouseButtonTitlebar(const gfx::Point & mouse_location,panel::ClickModifier modifier)1136 void GtkNativePanelTesting::PressLeftMouseButtonTitlebar(
1137     const gfx::Point& mouse_location, panel::ClickModifier modifier) {
1138 
1139   GdkEvent* event = gdk_event_new(GDK_BUTTON_PRESS);
1140   event->button.button = 1;
1141   event->button.x_root = mouse_location.x();
1142   event->button.y_root = mouse_location.y();
1143   if (modifier == panel::APPLY_TO_ALL)
1144     event->button.state |= GDK_CONTROL_MASK;
1145   panel_gtk_->OnTitlebarButtonPressEvent(
1146       NULL, reinterpret_cast<GdkEventButton*>(event));
1147   gdk_event_free(event);
1148   base::MessageLoopForUI::current()->RunUntilIdle();
1149 }
1150 
ReleaseMouseButtonTitlebar(panel::ClickModifier modifier)1151 void GtkNativePanelTesting::ReleaseMouseButtonTitlebar(
1152     panel::ClickModifier modifier) {
1153   GdkEvent* event = gdk_event_new(GDK_BUTTON_RELEASE);
1154   event->button.button = 1;
1155   if (modifier == panel::APPLY_TO_ALL)
1156     event->button.state |= GDK_CONTROL_MASK;
1157   if (panel_gtk_->drag_helper_.get()) {
1158     panel_gtk_->drag_helper_->OnButtonReleaseEvent(
1159         NULL, reinterpret_cast<GdkEventButton*>(event));
1160   } else {
1161     panel_gtk_->OnTitlebarButtonReleaseEvent(
1162         NULL, reinterpret_cast<GdkEventButton*>(event));
1163   }
1164   gdk_event_free(event);
1165   base::MessageLoopForUI::current()->RunUntilIdle();
1166 }
1167 
DragTitlebar(const gfx::Point & mouse_location)1168 void GtkNativePanelTesting::DragTitlebar(const gfx::Point& mouse_location) {
1169   if (!panel_gtk_->drag_helper_.get())
1170     return;
1171   GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY);
1172   event->motion.x_root = mouse_location.x();
1173   event->motion.y_root = mouse_location.y();
1174   panel_gtk_->drag_helper_->OnMouseMoveEvent(
1175       NULL, reinterpret_cast<GdkEventMotion*>(event));
1176   gdk_event_free(event);
1177   base::MessageLoopForUI::current()->RunUntilIdle();
1178 }
1179 
CancelDragTitlebar()1180 void GtkNativePanelTesting::CancelDragTitlebar() {
1181   if (!panel_gtk_->drag_helper_.get())
1182     return;
1183   panel_gtk_->drag_helper_->OnGrabBrokenEvent(NULL, NULL);
1184   base::MessageLoopForUI::current()->RunUntilIdle();
1185 }
1186 
FinishDragTitlebar()1187 void GtkNativePanelTesting::FinishDragTitlebar() {
1188   if (!panel_gtk_->drag_helper_.get())
1189     return;
1190   ReleaseMouseButtonTitlebar(panel::NO_MODIFIER);
1191 }
1192 
VerifyDrawingAttention() const1193 bool GtkNativePanelTesting::VerifyDrawingAttention() const {
1194   return panel_gtk_->IsDrawingAttention();
1195 }
1196 
VerifyActiveState(bool is_active)1197 bool GtkNativePanelTesting::VerifyActiveState(bool is_active) {
1198   return gtk_window_is_active(panel_gtk_->GetNativePanelWindow()) == is_active;
1199 }
1200 
VerifyAppIcon() const1201 bool GtkNativePanelTesting::VerifyAppIcon() const {
1202   GdkPixbuf* icon = gtk_window_get_icon(panel_gtk_->GetNativePanelWindow());
1203   return icon &&
1204          gdk_pixbuf_get_width(icon) == panel::kPanelAppIconSize &&
1205          gdk_pixbuf_get_height(icon) == panel::kPanelAppIconSize;
1206 }
1207 
VerifySystemMinimizeState() const1208 bool GtkNativePanelTesting::VerifySystemMinimizeState() const {
1209   // TODO(jianli): to be implemented.
1210   return true;
1211 }
1212 
IsWindowVisible() const1213 bool GtkNativePanelTesting::IsWindowVisible() const {
1214   GdkWindow* gdk_window =
1215       gtk_widget_get_window(GTK_WIDGET(panel_gtk_->GetNativePanelWindow()));
1216   return GDK_IS_WINDOW(gdk_window) && gdk_window_is_visible(gdk_window);
1217 }
1218 
IsWindowSizeKnown() const1219 bool GtkNativePanelTesting::IsWindowSizeKnown() const {
1220   return !GetFrameSize().IsEmpty();
1221 }
1222 
IsAnimatingBounds() const1223 bool GtkNativePanelTesting::IsAnimatingBounds() const {
1224   return false;
1225 }
1226 
IsButtonVisible(panel::TitlebarButtonType button_type) const1227 bool GtkNativePanelTesting::IsButtonVisible(
1228     panel::TitlebarButtonType button_type) const {
1229   PanelTitlebarGtk* titlebar = panel_gtk_->titlebar();
1230   CustomDrawButton* button;
1231   switch (button_type) {
1232     case panel::CLOSE_BUTTON:
1233       button = titlebar->close_button();
1234       break;
1235     case panel::MINIMIZE_BUTTON:
1236       button = titlebar->minimize_button();
1237       break;
1238     case panel::RESTORE_BUTTON:
1239       button = titlebar->restore_button();
1240       break;
1241     default:
1242       NOTREACHED();
1243       return false;
1244   }
1245   return gtk_widget_get_visible(button->widget());
1246 }
1247 
GetWindowCornerStyle() const1248 panel::CornerStyle GtkNativePanelTesting::GetWindowCornerStyle() const {
1249   return panel_gtk_->corner_style_;
1250 }
1251 
EnsureApplicationRunOnForeground()1252 bool GtkNativePanelTesting::EnsureApplicationRunOnForeground() {
1253   // Not needed on GTK.
1254   return true;
1255 }
1256