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.
5 #include "chrome/browser/ui/gtk/panels/panel_gtk.h"
8 #include <gdk/gdkkeysyms.h>
9 #include <X11/XF86keysym.h>
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"
45 using content::NativeWebKeyboardEvent
;
46 using content::WebContents
;
50 const char* kPanelWindowKey
= "__PANEL_GTK__";
52 // The number of milliseconds between loading animation frames.
53 const int kLoadingAnimationFrameTimeMs
= 30;
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;
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);
76 // Set minimium width for window really small.
77 const int kMinWindowWidth
= 26;
79 // Table of supported accelerators in Panels.
80 const struct AcceleratorMapping
{
83 GdkModifierType modifier_type
;
84 } kAcceleratorMap
[] = {
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
) },
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) },
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) },
118 { GDK_c
, IDC_COPY
, GDK_CONTROL_MASK
},
119 { GDK_x
, IDC_CUT
, GDK_CONTROL_MASK
},
120 { GDK_v
, IDC_PASTE
, GDK_CONTROL_MASK
},
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
) },
130 // Table of accelerator mappings to command ids.
131 typedef std::map
<ui::Accelerator
, int> AcceleratorMap
;
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
;
143 return accelerator_table
;
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()));
152 const gfx::Image
GetActiveBackgroundDefaultImage() {
153 CR_DEFINE_STATIC_LOCAL(gfx::Image
, image
, ());
155 image
= CreateImageForColor(kActiveBackgroundDefaultColor
);
159 gfx::Image
GetInactiveBackgroundDefaultImage() {
160 CR_DEFINE_STATIC_LOCAL(gfx::Image
, image
, ());
162 image
= CreateImageForColor(kInactiveBackgroundDefaultColor
);
166 gfx::Image
GetAttentionBackgroundDefaultImage() {
167 CR_DEFINE_STATIC_LOCAL(gfx::Image
, image
, ());
169 image
= CreateImageForColor(kAttentionBackgroundDefaultColor
);
173 gfx::Image
GetMinimizeBackgroundDefaultImage() {
174 CR_DEFINE_STATIC_LOCAL(gfx::Image
, image
, ());
176 image
= CreateImageForColor(kMinimizeBackgroundDefaultColor
);
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
);
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
, ());
195 void SetFrameSize(const gfx::Size
& new_size
) {
196 gfx::Size
& frame_size
= GetFrameSize();
197 frame_size
.SetSize(new_size
.width(), new_size
.height());
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
);
211 PanelGtk::PanelGtk(Panel
* panel
, const gfx::Rect
& bounds
, bool always_on_top
)
214 always_on_top_(always_on_top
),
216 paint_state_(PAINT_AS_INACTIVE
),
217 is_drawing_attention_(false),
219 is_active_(!ui::ActiveWindowWatcherX::WMSupportsActivation()),
220 is_minimized_(false),
222 window_container_(NULL
),
224 render_area_event_box_(NULL
),
225 contents_expanded_(NULL
),
227 corner_style_(panel::ALL_ROUNDED
) {
230 PanelGtk::~PanelGtk() {
231 ui::ActiveWindowWatcherX::RemoveObserver(this);
234 void PanelGtk::Init() {
235 ui::ActiveWindowWatcherX::AddObserver(this);
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);
243 // Disable the resize gripper on Ubuntu.
244 gtk_window_util::DisableResizeGrip(window_
);
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_
));
251 // Set minimum height for the window.
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
);
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);
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_
);
279 // TODO(jennb): add GlobalMenuBar after refactoring out Browser.
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_
);
293 // Build the titlebar.
294 titlebar_
.reset(new PanelTitlebarGtk(this));
296 gtk_box_pack_start(GTK_BOX(window_vbox_
), titlebar_
->widget(), FALSE
, FALSE
,
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);
303 contents_expanded_
= gtk_expanded_container_new();
304 gtk_widget_show(contents_expanded_
);
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
,
311 gtk_container_add(GTK_CONTAINER(render_area_event_box_
),
313 gtk_widget_show(render_area_event_box_
);
314 gtk_box_pack_end(GTK_BOX(window_vbox_
), render_area_event_box_
,
317 gtk_container_add(GTK_CONTAINER(window_
), window_container_
);
318 gtk_widget_show(window_container_
);
320 ConnectAccelerators();
321 SetPanelAlwaysOnTop(always_on_top_
);
324 void PanelGtk::SetWindowCornerStyle(panel::CornerStyle corner_style
) {
325 corner_style_
= corner_style
;
329 void PanelGtk::MinimizePanelBySystem() {
330 gtk_window_iconify(window_
);
333 bool PanelGtk::IsPanelMinimizedBySystem() const {
334 return is_minimized_
;
337 bool PanelGtk::IsPanelShownOnActiveDesktop() const {
338 // IsWindowVisible checks _NET_WM_DESKTOP.
339 if (!ui::IsWindowVisible(ui::GetX11WindowFromGtkWidget(GTK_WIDGET(window_
))))
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
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_
)),
353 return abs(win_x
- bounds_
.x()) < display_area
.width() &&
354 abs(win_y
- bounds_
.y()) < display_area
.height();
357 void PanelGtk::ShowShadow(bool show
) {
358 // Shadow is not supported for GTK panel.
361 void PanelGtk::UpdateWindowShape() {
362 int width
= configure_size_
.width();
363 int height
= configure_size_
.height();
364 if (!width
|| !height
)
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
);
374 GdkRectangle top_rect
= { 0, 0, width
, 3 };
375 mask
= gdk_region_rectangle(&top_rect
);
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
);
386 GdkRectangle mid_rect
= { 0, 3, width
, height
- 3 };
387 gdk_region_union_with_rect(mask
, &mid_rect
);
390 gdk_window_shape_combine_region(
391 gtk_widget_get_window(GTK_WIDGET(window_
)), mask
, 0, 0);
393 gdk_region_destroy(mask
);
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_
)
404 configure_size_
= new_size
;
408 if (!GetFrameSize().IsEmpty())
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();
417 content::NotificationService::current()->Notify(
418 chrome::NOTIFICATION_PANEL_WINDOW_SIZE_KNOWN
,
419 content::Source
<Panel
>(panel_
.get()),
420 content::NotificationService::NoDetails());
425 gboolean
PanelGtk::OnWindowState(GtkWidget
* widget
,
426 GdkEventWindowState
* event
) {
427 is_minimized_
= event
->new_window_state
& GDK_WINDOW_STATE_ICONIFIED
;
431 void PanelGtk::ConnectAccelerators() {
432 accel_group_
= gtk_accel_group_new();
433 gtk_window_add_accel_group(window_
, accel_group_
);
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(
440 ui::GetGdkKeyCodeForAccelerator(iter
->first
),
441 ui::GetGdkModifierForAccelerator(iter
->first
),
443 g_cclosure_new(G_CALLBACK(OnGtkAccelerator
),
444 GINT_TO_POINTER(iter
->second
), NULL
));
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(
456 ui::GetGdkKeyCodeForAccelerator(iter
->first
),
457 ui::GetGdkModifierForAccelerator(iter
->first
));
459 gtk_window_remove_accel_group(window_
, accel_group_
);
460 g_object_unref(accel_group_
);
465 gboolean
PanelGtk::OnGtkAccelerator(GtkAccelGroup
* accel_group
,
466 GObject
* acceleratable
,
468 GdkModifierType modifier
,
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
);
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().
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
);
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
)
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
;
509 } else if (y
>= height
- kResizeAreaCornerSize
&&
510 (resizability
& panel::RESIZABLE_BOTTOM_LEFT
)) {
511 *edge
= GDK_WINDOW_EDGE_SOUTH_WEST
;
514 } else if (x
>= width
- kFrameBorderThickness
) {
515 if (y
< kResizeAreaCornerSize
- kTopResizeAdjust
&&
516 (resizability
& panel::RESIZABLE_TOP_RIGHT
)) {
517 *edge
= GDK_WINDOW_EDGE_NORTH_EAST
;
519 } else if (y
>= height
- kResizeAreaCornerSize
&&
520 (resizability
& panel::RESIZABLE_BOTTOM_RIGHT
)) {
521 *edge
= GDK_WINDOW_EDGE_SOUTH_EAST
;
526 if (x
< kFrameBorderThickness
&& (resizability
& panel::RESIZABLE_LEFT
)) {
527 *edge
= GDK_WINDOW_EDGE_WEST
;
529 } else if (x
>= width
- kFrameBorderThickness
&&
530 (resizability
& panel::RESIZABLE_RIGHT
)) {
531 *edge
= GDK_WINDOW_EDGE_EAST
;
535 if (y
< kFrameBorderThickness
&& (resizability
& panel::RESIZABLE_TOP
)) {
536 *edge
= GDK_WINDOW_EDGE_NORTH
;
538 } else if (y
>= height
- kFrameBorderThickness
&&
539 (resizability
& panel::RESIZABLE_BOTTOM
)) {
540 *edge
= GDK_WINDOW_EDGE_SOUTH
;
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();
559 return GetInactiveBackgroundDefaultImage();
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
);
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
;
577 paint_state_
= PAINT_AS_ACTIVE
;
579 paint_state_
= PAINT_AS_INACTIVE
;
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
);
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);
614 return FALSE
; // Allow subwidgets to paint.
617 void PanelGtk::EnsureDragHelperCreated() {
618 if (drag_helper_
.get())
621 drag_helper_
.reset(new PanelDragGtk(panel_
.get()));
622 gtk_box_pack_end(GTK_BOX(window_vbox_
), drag_helper_
->widget(),
626 gboolean
PanelGtk::OnTitlebarButtonPressEvent(
627 GtkWidget
* widget
, GdkEventButton
* event
) {
628 if (event
->button
!= 1)
630 if (event
->type
!= GDK_BUTTON_PRESS
)
633 // If the panel is in a stack, bring all other panels in the stack to the
635 StackedPanelCollection
* stack
= panel_
->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);
654 gdk_window_raise(gtk_widget_get_window(GTK_WIDGET(window_
)));
657 EnsureDragHelperCreated();
658 drag_helper_
->InitialTitlebarMousePress(event
, titlebar_
->widget());
662 gboolean
PanelGtk::OnTitlebarButtonReleaseEvent(
663 GtkWidget
* widget
, GdkEventButton
* event
) {
664 if (event
->button
!= 1)
667 panel_
->OnTitlebarClicked((event
->state
& GDK_CONTROL_MASK
) ?
668 panel::APPLY_TO_ALL
: panel::NO_MODIFIER
);
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
)) {
679 frame_cursor_
= NULL
;
680 gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_
)), NULL
);
685 // Update the cursor if we're on the custom frame border.
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
;
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_
)),
702 gboolean
PanelGtk::OnButtonPressEvent(GtkWidget
* widget
,
703 GdkEventButton
* event
) {
704 if (event
->button
!= 1 || event
->type
!= GDK_BUTTON_PRESS
)
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.
713 // Make the button press coordinate relative to the panel window.
715 GdkWindow
* gdk_window
= gtk_widget_get_window(GTK_WIDGET(window_
));
716 gdk_window_get_origin(gdk_window
, &win_x
, &win_y
);
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
);
723 gdk_window_raise(gdk_window
);
724 EnsureDragHelperCreated();
725 // Resize cursor was set by PanelGtk when mouse moved over window edge.
727 gdk_window_get_cursor(gtk_widget_get_window(GTK_WIDGET(window_
)));
728 drag_helper_
->InitialWindowEdgeMousePress(event
, cursor
, edge
);
732 return FALSE
; // Continue to propagate the event.
735 void PanelGtk::ActiveWindowChanged(GdkWindow
* active_window
) {
736 // Do nothing if we're in the process of closing the panel window.
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.
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();
753 is_active_
= is_active
;
754 titlebar_
->UpdateTextColor();
756 panel_
->OnActiveStateChanged(is_active_
);
759 // Callback for the delete event. This event is fired when the user tries to
761 gboolean
PanelGtk::OnMainWindowDeleteEvent(GtkWidget
* widget
,
765 // Return true to prevent the gtk window from being destroyed. Close will
766 // destroy it for us.
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.
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));
781 void PanelGtk::ShowPanel() {
782 gtk_window_present(window_
);
786 void PanelGtk::ShowPanelInactive() {
787 gtk_window_set_focus_on_map(window_
, false);
788 gtk_widget_show(GTK_WIDGET(window_
));
792 void PanelGtk::RevealPanel() {
795 SetBoundsInternal(bounds_
);
798 gfx::Rect
PanelGtk::GetPanelBounds() const {
802 void PanelGtk::SetPanelBounds(const gfx::Rect
& bounds
) {
803 SetBoundsInternal(bounds
);
806 void PanelGtk::SetPanelBoundsInstantly(const gfx::Rect
& bounds
) {
807 SetBoundsInternal(bounds
);
810 void PanelGtk::SetBoundsInternal(const gfx::Rect
& bounds
) {
812 gdk_window_move_resize(gtk_widget_get_window(GTK_WIDGET(window_
)),
813 bounds
.x(), bounds
.y(),
814 bounds
.width(), bounds
.height());
819 titlebar_
->SendEnterNotifyToCloseButtonIfUnderMouse();
820 panel_
->manager()->OnPanelAnimationEnded(panel_
.get());
823 void PanelGtk::ClosePanel() {
824 // We're already closing. Do nothing.
828 if (!panel_
->ShouldCloseWindow())
831 if (drag_helper_
.get())
832 drag_helper_
.reset();
835 DisconnectAccelerators();
837 // Cancel any pending callback from the loading animation timer.
838 loading_animation_timer_
.Stop();
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();
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.
853 panel_
->OnNativePanelClosed();
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
);
861 void PanelGtk::ActivatePanel() {
862 gtk_window_present(window_
);
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_
)));
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();
883 // Then, try to pick other detached or stacked panel.
885 std::vector
<Panel
*> panels
=
886 panel_
->manager()->GetDetachedAndStackedPanels();
888 other_panel
= panel_
!= panels
.front() ? panels
.front() : panels
.back();
892 gtk_widget_get_window(GTK_WIDGET(window_
)),
893 other_panel
? gtk_widget_get_window(
894 GTK_WIDGET(other_panel
->GetNativeWindow())) : NULL
,
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.
902 // No way to deactive a GTK window. Pretend panel is deactivated
904 ActiveWindowChanged(NULL
);
907 bool PanelGtk::IsPanelActive() const {
911 void PanelGtk::PreventActivationByOS(bool prevent_activation
) {
912 gtk_window_set_accept_focus(window_
, !prevent_activation
);
915 gfx::NativeWindow
PanelGtk::GetNativePanelWindow() {
919 void PanelGtk::UpdatePanelTitleBar() {
920 TRACE_EVENT0("ui::gtk", "PanelGtk::UpdatePanelTitleBar");
921 base::string16 title
= panel_
->GetWindowTitle();
922 gtk_window_set_title(window_
, base::UTF16ToUTF8(title
).c_str());
923 titlebar_
->UpdateTitleAndIcon();
925 gfx::Image app_icon
= panel_
->app_icon();
926 if (!app_icon
.IsEmpty())
927 gtk_util::SetWindowIcon(window_
, panel_
->profile(), app_icon
.ToGdkPixbuf());
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
),
937 &PanelGtk::LoadingAnimationCallback
);
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();
948 void PanelGtk::LoadingAnimationCallback() {
949 titlebar_
->UpdateThrobber(panel_
->GetWebContents());
952 void PanelGtk::PanelWebContentsFocused(content::WebContents
* contents
) {
956 void PanelGtk::PanelCut() {
957 gtk_window_util::DoCut(window_
, panel_
->GetWebContents());
960 void PanelGtk::PanelCopy() {
961 gtk_window_util::DoCopy(window_
, panel_
->GetWebContents());
964 void PanelGtk::PanelPaste() {
965 gtk_window_util::DoPaste(window_
, panel_
->GetWebContents());
968 void PanelGtk::DrawAttention(bool draw_attention
) {
969 DCHECK((panel_
->attention_mode() & Panel::USE_PANEL_ATTENTION
) != 0);
971 if (is_drawing_attention_
== draw_attention
)
974 is_drawing_attention_
= draw_attention
;
976 titlebar_
->UpdateTextColor();
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
);
985 bool PanelGtk::IsDrawingAttention() const {
986 return is_drawing_attention_
;
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
);
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.
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
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();
1010 void PanelGtk::PanelExpansionStateChanging(
1011 Panel::ExpansionState old_state
, Panel::ExpansionState new_state
) {
1014 void PanelGtk::AttachWebContents(content::WebContents
* contents
) {
1017 gfx::NativeView widget
= contents
->GetView()->GetNativeView();
1019 gtk_container_add(GTK_CONTAINER(contents_expanded_
), widget
);
1020 gtk_widget_show(widget
);
1021 contents
->WasShown();
1025 void PanelGtk::DetachWebContents(content::WebContents
* contents
) {
1026 gfx::NativeView widget
= contents
->GetView()->GetNativeView();
1028 GtkWidget
* parent
= gtk_widget_get_parent(widget
);
1030 DCHECK_EQ(parent
, contents_expanded_
);
1031 gtk_container_remove(GTK_CONTAINER(contents_expanded_
), widget
);
1036 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());
1043 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());
1050 int PanelGtk::TitleOnlyHeight() const {
1051 gfx::Size
& frame_size
= GetFrameSize();
1052 if (!frame_size
.IsEmpty())
1053 return panel::kTitlebarHeight
;
1055 NOTREACHED() << "Checking title height before window allocated";
1059 bool PanelGtk::IsPanelAlwaysOnTop() const {
1060 return always_on_top_
;
1063 void PanelGtk::SetPanelAlwaysOnTop(bool on_top
) {
1064 always_on_top_
= on_top
;
1066 gtk_window_set_keep_above(window_
, on_top
);
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
);
1073 // Show always-on-top windows on all the virtual desktops.
1075 gtk_window_stick(window_
);
1077 gtk_window_unstick(window_
);
1080 void PanelGtk::UpdatePanelMinimizeRestoreButtonVisibility() {
1081 titlebar_
->UpdateMinimizeRestoreButtonVisibility();
1084 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
);
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_
)),
1100 // NativePanelTesting implementation.
1101 class GtkNativePanelTesting
: public NativePanelTesting
{
1103 explicit GtkNativePanelTesting(PanelGtk
* panel_gtk
);
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
;
1125 PanelGtk
* panel_gtk_
;
1128 NativePanelTesting
* PanelGtk::CreateNativePanelTesting() {
1129 return new GtkNativePanelTesting(this);
1132 GtkNativePanelTesting::GtkNativePanelTesting(PanelGtk
* panel_gtk
)
1133 : panel_gtk_(panel_gtk
) {
1136 void GtkNativePanelTesting::PressLeftMouseButtonTitlebar(
1137 const gfx::Point
& mouse_location
, panel::ClickModifier modifier
) {
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();
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
));
1161 panel_gtk_
->OnTitlebarButtonReleaseEvent(
1162 NULL
, reinterpret_cast<GdkEventButton
*>(event
));
1164 gdk_event_free(event
);
1165 base::MessageLoopForUI::current()->RunUntilIdle();
1168 void GtkNativePanelTesting::DragTitlebar(const gfx::Point
& mouse_location
) {
1169 if (!panel_gtk_
->drag_helper_
.get())
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();
1180 void GtkNativePanelTesting::CancelDragTitlebar() {
1181 if (!panel_gtk_
->drag_helper_
.get())
1183 panel_gtk_
->drag_helper_
->OnGrabBrokenEvent(NULL
, NULL
);
1184 base::MessageLoopForUI::current()->RunUntilIdle();
1187 void GtkNativePanelTesting::FinishDragTitlebar() {
1188 if (!panel_gtk_
->drag_helper_
.get())
1190 ReleaseMouseButtonTitlebar(panel::NO_MODIFIER
);
1193 bool GtkNativePanelTesting::VerifyDrawingAttention() const {
1194 return panel_gtk_
->IsDrawingAttention();
1197 bool GtkNativePanelTesting::VerifyActiveState(bool is_active
) {
1198 return gtk_window_is_active(panel_gtk_
->GetNativePanelWindow()) == is_active
;
1201 bool GtkNativePanelTesting::VerifyAppIcon() const {
1202 GdkPixbuf
* icon
= gtk_window_get_icon(panel_gtk_
->GetNativePanelWindow());
1204 gdk_pixbuf_get_width(icon
) == panel::kPanelAppIconSize
&&
1205 gdk_pixbuf_get_height(icon
) == panel::kPanelAppIconSize
;
1208 bool GtkNativePanelTesting::VerifySystemMinimizeState() const {
1209 // TODO(jianli): to be implemented.
1213 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
);
1219 bool GtkNativePanelTesting::IsWindowSizeKnown() const {
1220 return !GetFrameSize().IsEmpty();
1223 bool GtkNativePanelTesting::IsAnimatingBounds() const {
1227 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();
1235 case panel::MINIMIZE_BUTTON
:
1236 button
= titlebar
->minimize_button();
1238 case panel::RESTORE_BUTTON
:
1239 button
= titlebar
->restore_button();
1245 return gtk_widget_get_visible(button
->widget());
1248 panel::CornerStyle
GtkNativePanelTesting::GetWindowCornerStyle() const {
1249 return panel_gtk_
->corner_style_
;
1252 bool GtkNativePanelTesting::EnsureApplicationRunOnForeground() {
1253 // Not needed on GTK.