summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/panels/panel_gtk.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/ui/panels/panel_gtk.cc')
-rw-r--r--chrome/browser/ui/panels/panel_gtk.cc1173
1 files changed, 1171 insertions, 2 deletions
diff --git a/chrome/browser/ui/panels/panel_gtk.cc b/chrome/browser/ui/panels/panel_gtk.cc
index eeb6039..59bfebc 100644
--- a/chrome/browser/ui/panels/panel_gtk.cc
+++ b/chrome/browser/ui/panels/panel_gtk.cc
@@ -2,11 +2,1180 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "chrome/browser/ui/panels/panel_gtk.h"
+
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <X11/XF86keysym.h>
+
+#include "base/bind.h"
+#include "base/debug/trace_event.h"
#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/ui/app_modal_dialogs/app_modal_dialog_queue.h"
+#include "chrome/browser/ui/gtk/custom_button.h"
+#include "chrome/browser/ui/gtk/gtk_theme_service.h"
+#include "chrome/browser/ui/gtk/gtk_util.h"
+#include "chrome/browser/ui/gtk/gtk_window_util.h"
#include "chrome/browser/ui/panels/panel.h"
+#include "chrome/browser/ui/panels/panel_bounds_animation.h"
+#include "chrome/browser/ui/panels/panel_titlebar_gtk.h"
+#include "chrome/browser/ui/panels/panel_constants.h"
+#include "chrome/browser/ui/panels/panel_drag_gtk.h"
+#include "chrome/browser/ui/panels/panel_manager.h"
+#include "chrome/browser/web_applications/web_app.h"
+#include "chrome/common/chrome_notification_types.h"
+#include "content/public/browser/native_web_keyboard_event.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/web_contents.h"
+#include "grit/theme_resources.h"
+#include "grit/ui_resources.h"
+#include "ui/base/accelerators/accelerator_gtk.h"
+#include "ui/base/gtk/gtk_compat.h"
+#include "ui/base/gtk/gtk_expanded_container.h"
+#include "ui/base/gtk/gtk_hig_constants.h"
+#include "ui/base/x/active_window_watcher_x.h"
+#include "ui/gfx/canvas.h"
+#include "ui/gfx/image/cairo_cached_surface.h"
+#include "ui/gfx/image/image.h"
+
+using content::NativeWebKeyboardEvent;
+using content::WebContents;
+
+namespace {
+
+const char* kPanelWindowKey = "__PANEL_GTK__";
+
+// The number of milliseconds between loading animation frames.
+const int kLoadingAnimationFrameTimeMs = 30;
+
+// The frame border is only visible in restored mode and is hardcoded to 4 px
+// on each side regardless of the system window border size.
+const int kFrameBorderThickness = 4;
+// While resize areas on Windows are normally the same size as the window
+// borders, our top area is shrunk by 1 px to make it easier to move the window
+// around with our thinner top grabbable strip. (Incidentally, our side and
+// bottom resize areas don't match the frame border thickness either -- they
+// span the whole nonclient area, so there's no "dead zone" for the mouse.)
+const int kTopResizeAdjust = 1;
+// In the window corners, the resize areas don't actually expand bigger, but
+// the 16 px at the end of each edge triggers diagonal resizing.
+const int kResizeAreaCornerSize = 16;
+
+// Colors used to draw frame background under default theme.
+const SkColor kActiveBackgroundDefaultColor = SkColorSetRGB(0x3a, 0x3d, 0x3d);
+const SkColor kInactiveBackgroundDefaultColor = SkColorSetRGB(0x7a, 0x7c, 0x7c);
+const SkColor kAttentionBackgroundDefaultColor =
+ SkColorSetRGB(0xff, 0xab, 0x57);
+const SkColor kMinimizeBackgroundDefaultColor = SkColorSetRGB(0xf5, 0xf4, 0xf0);
+const SkColor kMinimizeBorderDefaultColor = SkColorSetRGB(0xc9, 0xc9, 0xc9);
+
+// Color used to draw the divider line between the titlebar and the client area.
+const SkColor kDividerColor = SkColorSetRGB(0x2a, 0x2c, 0x2c);
+
+// Set minimium width for window really small.
+const int kMinWindowWidth = 26;
+
+// Table of supported accelerators in Panels.
+const struct AcceleratorMapping {
+ guint keyval;
+ int command_id;
+ GdkModifierType modifier_type;
+} kAcceleratorMap[] = {
+ // Window controls.
+ { GDK_w, IDC_CLOSE_WINDOW, GDK_CONTROL_MASK },
+ { GDK_w, IDC_CLOSE_WINDOW,
+ GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) },
+ { GDK_q, IDC_EXIT, GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) },
+
+ // Zoom level.
+ { GDK_KP_Add, IDC_ZOOM_PLUS, GDK_CONTROL_MASK },
+ { GDK_plus, IDC_ZOOM_PLUS,
+ GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) },
+ { GDK_equal, IDC_ZOOM_PLUS, GDK_CONTROL_MASK },
+ { XF86XK_ZoomIn, IDC_ZOOM_PLUS, GdkModifierType(0) },
+ { GDK_KP_0, IDC_ZOOM_NORMAL, GDK_CONTROL_MASK },
+ { GDK_0, IDC_ZOOM_NORMAL, GDK_CONTROL_MASK },
+ { GDK_KP_Subtract, IDC_ZOOM_MINUS, GDK_CONTROL_MASK },
+ { GDK_minus, IDC_ZOOM_MINUS, GDK_CONTROL_MASK },
+ { GDK_underscore, IDC_ZOOM_MINUS,
+ GdkModifierType(GDK_CONTROL_MASK | GDK_SHIFT_MASK) },
+ { XF86XK_ZoomOut, IDC_ZOOM_MINUS, GdkModifierType(0) },
+
+ // Navigation.
+ { GDK_Escape, IDC_STOP, GdkModifierType(0) },
+ { XF86XK_Stop, IDC_STOP, GdkModifierType(0) },
+ { GDK_r, IDC_RELOAD, GDK_CONTROL_MASK },
+ { GDK_r, IDC_RELOAD_IGNORING_CACHE,
+ GdkModifierType(GDK_CONTROL_MASK|GDK_SHIFT_MASK) },
+ { GDK_F5, IDC_RELOAD, GdkModifierType(0) },
+ { GDK_F5, IDC_RELOAD_IGNORING_CACHE, GDK_CONTROL_MASK },
+ { GDK_F5, IDC_RELOAD_IGNORING_CACHE, GDK_SHIFT_MASK },
+ { XF86XK_Reload, IDC_RELOAD, GdkModifierType(0) },
+ { XF86XK_Refresh, IDC_RELOAD, GdkModifierType(0) },
+
+ // Editing.
+ { GDK_c, IDC_COPY, GDK_CONTROL_MASK },
+ { GDK_x, IDC_CUT, GDK_CONTROL_MASK },
+ { GDK_v, IDC_PASTE, GDK_CONTROL_MASK },
+};
+
+// Table of accelerator mappings to command ids.
+typedef std::map<ui::AcceleratorGtk, int> AcceleratorGtkMap;
+
+const AcceleratorGtkMap& GetAcceleratorTable() {
+ CR_DEFINE_STATIC_LOCAL(AcceleratorGtkMap, accelerator_table, ());
+ if (accelerator_table.empty()) {
+ for (size_t i = 0; i < arraysize(kAcceleratorMap); ++i) {
+ const AcceleratorMapping& entry = kAcceleratorMap[i];
+ ui::AcceleratorGtk accelerator(entry.keyval, entry.modifier_type);
+ accelerator_table[accelerator] = entry.command_id;
+ }
+ }
+ return accelerator_table;
+}
+
+gfx::Image* CreateImageForColor(SkColor color) {
+ gfx::Canvas canvas(gfx::Size(1, 1), ui::SCALE_FACTOR_100P, true);
+ canvas.DrawColor(color);
+ return new gfx::Image(gfx::ImageSkia(canvas.ExtractImageRep()));
+}
+
+const gfx::Image* GetActiveBackgroundDefaultImage() {
+ static gfx::Image* image = NULL;
+ if (!image)
+ image = CreateImageForColor(kActiveBackgroundDefaultColor);
+ return image;
+}
+
+const gfx::Image* GetInactiveBackgroundDefaultImage() {
+ static gfx::Image* image = NULL;
+ if (!image)
+ image = CreateImageForColor(kInactiveBackgroundDefaultColor);
+ return image;
+}
+
+const gfx::Image* GetAttentionBackgroundDefaultImage() {
+ static gfx::Image* image = NULL;
+ if (!image)
+ image = CreateImageForColor(kAttentionBackgroundDefaultColor);
+ return image;
+}
+
+const gfx::Image* GetMinimizeBackgroundDefaultImage() {
+ static gfx::Image* image = NULL;
+ if (!image)
+ image = CreateImageForColor(kMinimizeBackgroundDefaultColor);
+ return image;
+}
+
+// Used to stash a pointer to the Panel window inside the native
+// Gtk window for retrieval in static callbacks.
+GQuark GetPanelWindowQuarkKey() {
+ static GQuark quark = g_quark_from_static_string(kPanelWindowKey);
+ return quark;
+}
+
+// Size of window frame. Empty until first panel has been allocated
+// and sized. Frame size won't change for other panels so it can be
+// computed once for all panels.
+gfx::Size& g_frame_size_ = *(new gfx::Size());
+
+}
// static
NativePanel* Panel::CreateNativePanel(Panel* panel, const gfx::Rect& bounds) {
- NOTIMPLEMENTED();
- return NULL;
+ PanelGtk* panel_gtk = new PanelGtk(panel, bounds);
+ panel_gtk->Init();
+ return panel_gtk;
+}
+
+PanelGtk::PanelGtk(Panel* panel, const gfx::Rect& bounds)
+ : panel_(panel),
+ bounds_(bounds),
+ is_shown_(false),
+ paint_state_(PAINT_AS_INACTIVE),
+ is_drawing_attention_(false),
+ frame_cursor_(NULL),
+ is_active_(!ui::ActiveWindowWatcherX::WMSupportsActivation()),
+ window_(NULL),
+ window_container_(NULL),
+ window_vbox_(NULL),
+ render_area_event_box_(NULL),
+ contents_expanded_(NULL),
+ accel_group_(NULL) {
+}
+
+PanelGtk::~PanelGtk() {
+ ui::ActiveWindowWatcherX::RemoveObserver(this);
+}
+
+void PanelGtk::Init() {
+ ui::ActiveWindowWatcherX::AddObserver(this);
+
+ window_ = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
+ g_object_set_qdata(G_OBJECT(window_), GetPanelWindowQuarkKey(), this);
+ gtk_widget_add_events(GTK_WIDGET(window_), GDK_BUTTON_PRESS_MASK |
+ GDK_POINTER_MOTION_MASK);
+ gtk_window_set_decorated(window_, false);
+ // Keep the window always on top.
+ gtk_window_set_keep_above(window_, TRUE);
+ // Show the window on all the virtual desktops.
+ gtk_window_stick(window_);
+ // Do not show an icon in the task bar. Window operations such as close,
+ // minimize etc. can only be done from the panel UI.
+ gtk_window_set_skip_taskbar_hint(window_, TRUE);
+
+ // Disable the resize gripper on Ubuntu.
+ gtk_window_util::DisableResizeGrip(window_);
+
+ // Add this window to its own unique window group to allow for
+ // window-to-parent modality.
+ gtk_window_group_add_window(gtk_window_group_new(), window_);
+ g_object_unref(gtk_window_get_group(window_));
+
+ // Set minimum height for the window.
+ GdkGeometry hints;
+ hints.min_height = panel::kMinimizedPanelHeight;
+ hints.min_width = kMinWindowWidth;
+ gtk_window_set_geometry_hints(
+ window_, GTK_WIDGET(window_), &hints, GDK_HINT_MIN_SIZE);
+
+ // Connect signal handlers to the window.
+ g_signal_connect(window_, "delete-event",
+ G_CALLBACK(OnMainWindowDeleteEventThunk), this);
+ g_signal_connect(window_, "destroy",
+ G_CALLBACK(OnMainWindowDestroyThunk), this);
+ g_signal_connect(window_, "configure-event",
+ G_CALLBACK(OnConfigureThunk), this);
+ g_signal_connect(window_, "key-press-event",
+ G_CALLBACK(OnKeyPressThunk), this);
+ g_signal_connect(window_, "motion-notify-event",
+ G_CALLBACK(OnMouseMoveEventThunk), this);
+ g_signal_connect(window_, "button-press-event",
+ G_CALLBACK(OnButtonPressEventThunk), this);
+
+ // This vbox contains the titlebar and the render area, but not
+ // the custom frame border.
+ window_vbox_ = gtk_vbox_new(FALSE, 0);
+ gtk_widget_show(window_vbox_);
+
+ // TODO(jennb): add GlobalMenuBar after refactoring out Browser.
+
+ // The window container draws the custom browser frame.
+ window_container_ = gtk_alignment_new(0.0, 0.0, 1.0, 1.0);
+ gtk_widget_set_name(window_container_, "chrome-custom-frame-border");
+ gtk_widget_set_app_paintable(window_container_, TRUE);
+ gtk_widget_set_double_buffered(window_container_, FALSE);
+ gtk_widget_set_redraw_on_allocate(window_container_, TRUE);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(window_container_), 1,
+ kFrameBorderThickness, kFrameBorderThickness, kFrameBorderThickness);
+ g_signal_connect(window_container_, "expose-event",
+ G_CALLBACK(OnCustomFrameExposeThunk), this);
+ gtk_container_add(GTK_CONTAINER(window_container_), window_vbox_);
+
+ // Build the titlebar.
+ titlebar_.reset(new PanelTitlebarGtk(this));
+ titlebar_->Init();
+ gtk_box_pack_start(GTK_BOX(window_vbox_), titlebar_->widget(), FALSE, FALSE,
+ 0);
+ g_signal_connect(titlebar_->widget(), "button-press-event",
+ G_CALLBACK(OnTitlebarButtonPressEventThunk), this);
+ g_signal_connect(titlebar_->widget(), "button-release-event",
+ G_CALLBACK(OnTitlebarButtonReleaseEventThunk), this);
+
+ contents_expanded_ = gtk_expanded_container_new();
+ gtk_widget_show(contents_expanded_);
+
+ render_area_event_box_ = gtk_event_box_new();
+ // Set a white background so during startup the user sees white in the
+ // content area before we get a WebContents in place.
+ gtk_widget_modify_bg(render_area_event_box_, GTK_STATE_NORMAL,
+ &ui::kGdkWhite);
+ gtk_container_add(GTK_CONTAINER(render_area_event_box_),
+ contents_expanded_);
+ gtk_widget_show(render_area_event_box_);
+ gtk_box_pack_end(GTK_BOX(window_vbox_), render_area_event_box_,
+ TRUE, TRUE, 0);
+
+ gtk_container_add(GTK_CONTAINER(window_), window_container_);
+ gtk_widget_show(window_container_);
+
+ ConnectAccelerators();
+}
+
+void PanelGtk::UpdateWindowShape(int width, int height) {
+ // For panels, only top corners are rounded. The bottom corners are not
+ // rounded because panels are aligned to the bottom edge of the screen.
+ GdkRectangle top_top_rect = { 3, 0, width - 6, 1 };
+ GdkRectangle top_mid_rect = { 1, 1, width - 2, 2 };
+ GdkRectangle mid_rect = { 0, 3, width, height - 3 };
+ GdkRegion* mask = gdk_region_rectangle(&top_top_rect);
+ gdk_region_union_with_rect(mask, &top_mid_rect);
+ gdk_region_union_with_rect(mask, &mid_rect);
+ gdk_window_shape_combine_region(
+ gtk_widget_get_window(GTK_WIDGET(window_)), mask, 0, 0);
+ if (mask)
+ gdk_region_destroy(mask);
+}
+
+gboolean PanelGtk::OnConfigure(GtkWidget* widget,
+ GdkEventConfigure* event) {
+ // When the window moves, we'll get multiple configure-event signals. We can
+ // also get events when the bounds haven't changed, but the window's stacking
+ // has, which we aren't interested in. http://crbug.com/70125
+ gfx::Size new_size(event->width, event->height);
+ if (new_size == configure_size_)
+ return FALSE;
+
+ UpdateWindowShape(event->width, event->height);
+ configure_size_ = new_size;
+
+ if (!g_frame_size_.IsEmpty())
+ return FALSE;
+
+ // Save the frame size allocated by the system after as the
+ // frame size will be affected when we shrink the panel smaller
+ // than the frame (e.g. when the panel is minimized).
+ g_frame_size_ = GetNonClientFrameSize();
+ panel_->OnWindowSizeAvailable();
+
+ content::NotificationService::current()->Notify(
+ chrome::NOTIFICATION_PANEL_WINDOW_SIZE_KNOWN,
+ content::Source<Panel>(panel_.get()),
+ content::NotificationService::NoDetails());
+
+ return FALSE;
+}
+
+void PanelGtk::ConnectAccelerators() {
+ accel_group_ = gtk_accel_group_new();
+ gtk_window_add_accel_group(window_, accel_group_);
+
+ const AcceleratorGtkMap& accelerator_table = GetAcceleratorTable();
+ for (AcceleratorGtkMap::const_iterator iter = accelerator_table.begin();
+ iter != accelerator_table.end(); ++iter) {
+ gtk_accel_group_connect(
+ accel_group_,
+ iter->first.GetGdkKeyCode(),
+ static_cast<GdkModifierType>(iter->first.modifiers()),
+ GtkAccelFlags(0),
+ g_cclosure_new(G_CALLBACK(OnGtkAccelerator),
+ GINT_TO_POINTER(iter->second), NULL));
+ }
+}
+
+void PanelGtk::DisconnectAccelerators() {
+ // Disconnecting the keys we connected to our accelerator group frees the
+ // closures allocated in ConnectAccelerators.
+ const AcceleratorGtkMap& accelerator_table = GetAcceleratorTable();
+ for (AcceleratorGtkMap::const_iterator iter = accelerator_table.begin();
+ iter != accelerator_table.end(); ++iter) {
+ gtk_accel_group_disconnect_key(accel_group_,
+ iter->first.GetGdkKeyCode(),
+ static_cast<GdkModifierType>(iter->first.modifiers()));
+ }
+ gtk_window_remove_accel_group(window_, accel_group_);
+ g_object_unref(accel_group_);
+ accel_group_ = NULL;
+}
+
+// static
+gboolean PanelGtk::OnGtkAccelerator(GtkAccelGroup* accel_group,
+ GObject* acceleratable,
+ guint keyval,
+ GdkModifierType modifier,
+ void* user_data) {
+ DCHECK(acceleratable);
+ int command_id = GPOINTER_TO_INT(user_data);
+ PanelGtk* panel_gtk = static_cast<PanelGtk*>(
+ g_object_get_qdata(acceleratable, GetPanelWindowQuarkKey()));
+ return panel_gtk->panel()->ExecuteCommandIfEnabled(command_id);
+}
+
+gboolean PanelGtk::OnKeyPress(GtkWidget* widget, GdkEventKey* event) {
+ // No way to deactivate a window in GTK, so ignore input if window
+ // is supposed to be 'inactive'. See comments in DeactivatePanel().
+ if (!is_active_)
+ return TRUE;
+
+ // Propagate the key event to child widget first, so we don't override
+ // their accelerators.
+ if (!gtk_window_propagate_key_event(GTK_WINDOW(widget), event)) {
+ if (!gtk_window_activate_key(GTK_WINDOW(widget), event)) {
+ gtk_bindings_activate_event(GTK_OBJECT(widget), event);
+ }
+ }
+ return TRUE;
+}
+
+bool PanelGtk::UsingDefaultTheme() const {
+ // No theme is provided for attention painting.
+ if (paint_state_ == PAINT_FOR_ATTENTION)
+ return true;
+
+ GtkThemeService* theme_provider = GtkThemeService::GetFrom(panel_->profile());
+ return theme_provider->UsingDefaultTheme() ||
+ theme_provider->UsingNativeTheme();
+}
+
+bool PanelGtk::GetWindowEdge(int x, int y, GdkWindowEdge* edge) const {
+ // Only detect the window edge when panels can be resized by the user.
+ // This method is used by the base class to detect when the cursor has
+ // hit the window edge in order to change the cursor to a resize cursor
+ // and to detect when to initiate a resize drag.
+ panel::Resizability resizability = panel_->CanResizeByMouse();
+ if (panel::NOT_RESIZABLE == resizability)
+ return false;
+
+ if (x < kFrameBorderThickness) {
+ // Left edge.
+ if (y < kResizeAreaCornerSize - kTopResizeAdjust) {
+ *edge = GDK_WINDOW_EDGE_NORTH_WEST;
+ } else if (y < bounds_.height() - kResizeAreaCornerSize) {
+ *edge = GDK_WINDOW_EDGE_WEST;
+ } else {
+ *edge = GDK_WINDOW_EDGE_SOUTH_WEST;
+ }
+ } else if (x < bounds_.width() - kFrameBorderThickness) {
+ if (y < kFrameBorderThickness - kTopResizeAdjust) {
+ // Top edge.
+ if (x < kResizeAreaCornerSize) {
+ *edge = GDK_WINDOW_EDGE_NORTH_WEST;
+ } else if (x < bounds_.width() - kResizeAreaCornerSize) {
+ *edge = GDK_WINDOW_EDGE_NORTH;
+ } else {
+ *edge = GDK_WINDOW_EDGE_NORTH_EAST;
+ }
+ } else if (y < bounds_.height() - kFrameBorderThickness) {
+ // Ignore the middle content area.
+ return false;
+ } else {
+ // Bottom edge.
+ if (x < kResizeAreaCornerSize) {
+ *edge = GDK_WINDOW_EDGE_SOUTH_WEST;
+ } else if (x < bounds_.width() - kResizeAreaCornerSize) {
+ *edge = GDK_WINDOW_EDGE_SOUTH;
+ } else {
+ *edge = GDK_WINDOW_EDGE_SOUTH_EAST;
+ }
+ }
+ } else {
+ // Right edge.
+ if (y < kResizeAreaCornerSize - kTopResizeAdjust) {
+ *edge = GDK_WINDOW_EDGE_NORTH_EAST;
+ } else if (y < bounds_.height() - kResizeAreaCornerSize) {
+ *edge = GDK_WINDOW_EDGE_EAST;
+ } else {
+ *edge = GDK_WINDOW_EDGE_SOUTH_EAST;
+ }
+ }
+
+ // Special handling if bottom edge is not resizable.
+ if (panel::RESIZABLE_ALL_SIDES_EXCEPT_BOTTOM == resizability) {
+ if (*edge == GDK_WINDOW_EDGE_SOUTH)
+ return FALSE;
+ if (*edge == GDK_WINDOW_EDGE_SOUTH_WEST)
+ *edge = GDK_WINDOW_EDGE_WEST;
+ else if (*edge == GDK_WINDOW_EDGE_SOUTH_EAST)
+ *edge = GDK_WINDOW_EDGE_EAST;
+ }
+
+ return true;
+}
+
+const gfx::Image* PanelGtk::GetFrameBackground() const {
+ return UsingDefaultTheme() ?
+ GetDefaultFrameBackground() : GetThemedFrameBackground();
+}
+
+const gfx::Image* PanelGtk::GetDefaultFrameBackground() const {
+ switch (paint_state_) {
+ case PAINT_AS_INACTIVE:
+ return GetInactiveBackgroundDefaultImage();
+ case PAINT_AS_ACTIVE:
+ return GetActiveBackgroundDefaultImage();
+ case PAINT_AS_MINIMIZED:
+ return GetMinimizeBackgroundDefaultImage();
+ case PAINT_FOR_ATTENTION:
+ return GetAttentionBackgroundDefaultImage();
+ default:
+ NOTREACHED();
+ return GetInactiveBackgroundDefaultImage();
+ }
+}
+
+const gfx::Image* PanelGtk::GetThemedFrameBackground() const {
+ GtkThemeService* theme_provider = GtkThemeService::GetFrom(panel_->profile());
+ return theme_provider->GetImageNamed(paint_state_ == PAINT_AS_ACTIVE ?
+ IDR_THEME_TOOLBAR : IDR_THEME_TAB_BACKGROUND);
+}
+
+gboolean PanelGtk::OnCustomFrameExpose(GtkWidget* widget,
+ GdkEventExpose* event) {
+ TRACE_EVENT0("ui::gtk", "PanelGtk::OnCustomFrameExpose");
+ cairo_t* cr = gdk_cairo_create(gtk_widget_get_window(widget));
+ gdk_cairo_rectangle(cr, &event->area);
+ cairo_clip(cr);
+
+ // Update the painting state.
+ int window_height = gdk_window_get_height(gtk_widget_get_window(widget));
+ if (is_drawing_attention_)
+ paint_state_ = PAINT_FOR_ATTENTION;
+ else if (window_height <= panel::kMinimizedPanelHeight)
+ paint_state_ = PAINT_AS_MINIMIZED;
+ else if (is_active_)
+ paint_state_ = PAINT_AS_ACTIVE;
+ else
+ paint_state_ = PAINT_AS_INACTIVE;
+
+ // Draw the background.
+ gfx::CairoCachedSurface* surface = GetFrameBackground()->ToCairo();
+ surface->SetSource(cr, widget, 0, 0);
+ cairo_pattern_set_extend(cairo_get_source(cr), CAIRO_EXTEND_REPEAT);
+ cairo_rectangle(cr, event->area.x, event->area.y,
+ event->area.width, event->area.height);
+ cairo_fill(cr);
+
+ // Draw the divider only if we're showing more than titlebar.
+ if (window_height > panel::kTitlebarHeight) {
+ cairo_set_source_rgb(cr,
+ SkColorGetR(kDividerColor) / 255.0,
+ SkColorGetG(kDividerColor) / 255.0,
+ SkColorGetB(kDividerColor) / 255.0);
+ cairo_rectangle(cr, 0, panel::kTitlebarHeight - 1, bounds_.width(), 1);
+ cairo_fill(cr);
+ }
+
+ // Draw the border for the minimized panel only.
+ if (paint_state_ == PAINT_AS_MINIMIZED) {
+ cairo_move_to(cr, 0, 3);
+ cairo_line_to(cr, 1, 2);
+ cairo_line_to(cr, 1, 1);
+ cairo_line_to(cr, 2, 1);
+ cairo_line_to(cr, 3, 0);
+ cairo_line_to(cr, event->area.width - 3, 0);
+ cairo_line_to(cr, event->area.width - 2, 1);
+ cairo_line_to(cr, event->area.width - 1, 1);
+ cairo_line_to(cr, event->area.width - 1, 2);
+ cairo_line_to(cr, event->area.width - 1, 3);
+ cairo_line_to(cr, event->area.width - 1, event->area.height - 1);
+ cairo_line_to(cr, 0, event->area.height - 1);
+ cairo_close_path(cr);
+ cairo_set_source_rgb(cr,
+ SkColorGetR(kMinimizeBorderDefaultColor) / 255.0,
+ SkColorGetG(kMinimizeBorderDefaultColor) / 255.0,
+ SkColorGetB(kMinimizeBorderDefaultColor) / 255.0);
+ cairo_set_line_width(cr, 1.0);
+ cairo_stroke(cr);
+ }
+
+ cairo_destroy(cr);
+
+ return FALSE; // Allow subwidgets to paint.
+}
+
+void PanelGtk::EnsureDragHelperCreated() {
+ if (drag_helper_.get())
+ return;
+
+ drag_helper_.reset(new PanelDragGtk(panel_.get()));
+ gtk_box_pack_end(GTK_BOX(window_vbox_), drag_helper_->widget(),
+ FALSE, FALSE, 0);
+}
+
+gboolean PanelGtk::OnTitlebarButtonPressEvent(
+ GtkWidget* widget, GdkEventButton* event) {
+ if (event->button != 1)
+ return TRUE;
+ if (event->type != GDK_BUTTON_PRESS)
+ return TRUE;
+
+ gdk_window_raise(gtk_widget_get_window(GTK_WIDGET(window_)));
+ EnsureDragHelperCreated();
+ drag_helper_->InitialTitlebarMousePress(event, titlebar_->widget());
+ return TRUE;
+}
+
+gboolean PanelGtk::OnTitlebarButtonReleaseEvent(
+ GtkWidget* widget, GdkEventButton* event) {
+ if (event->button != 1)
+ return TRUE;
+
+ panel_->OnTitlebarClicked((event->state & GDK_CONTROL_MASK) ?
+ panel::APPLY_TO_ALL : panel::NO_MODIFIER);
+ return TRUE;
+}
+
+gboolean PanelGtk::OnMouseMoveEvent(GtkWidget* widget,
+ GdkEventMotion* event) {
+ // This method is used to update the mouse cursor when over the edge of the
+ // custom frame. If we're over some other widget, do nothing.
+ if (event->window != gtk_widget_get_window(widget)) {
+ // Reset the cursor.
+ if (frame_cursor_) {
+ frame_cursor_ = NULL;
+ gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)), NULL);
+ }
+ return FALSE;
+ }
+
+ // Update the cursor if we're on the custom frame border.
+ GdkWindowEdge edge;
+ bool has_hit_edge = GetWindowEdge(static_cast<int>(event->x),
+ static_cast<int>(event->y), &edge);
+ GdkCursorType new_cursor = has_hit_edge ?
+ gtk_window_util::GdkWindowEdgeToGdkCursorType(edge) : GDK_LAST_CURSOR;
+ GdkCursorType last_cursor =
+ frame_cursor_ ? frame_cursor_->type : GDK_LAST_CURSOR;
+
+ if (last_cursor != new_cursor) {
+ frame_cursor_ = has_hit_edge ? gfx::GetCursor(new_cursor) : NULL;
+ gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(window_)),
+ frame_cursor_);
+ }
+ return FALSE;
+}
+
+gboolean PanelGtk::OnButtonPressEvent(GtkWidget* widget,
+ GdkEventButton* event) {
+ if (event->button != 1 || event->type != GDK_BUTTON_PRESS)
+ return FALSE;
+
+ // No way to deactivate a window in GTK, so we pretended it is deactivated.
+ // See comments in DeactivatePanel().
+ // Mouse click anywhere in window should re-activate window so do it now.
+ if (!is_active_)
+ panel_->Activate();
+
+ // Make the button press coordinate relative to the panel window.
+ int win_x, win_y;
+ GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window_));
+ gdk_window_get_origin(gdk_window, &win_x, &win_y);
+
+ GdkWindowEdge edge;
+ gfx::Point point(static_cast<int>(event->x_root - win_x),
+ static_cast<int>(event->y_root - win_y));
+ bool has_hit_edge = GetWindowEdge(point.x(), point.y(), &edge);
+ if (has_hit_edge) {
+ gdk_window_raise(gdk_window);
+ EnsureDragHelperCreated();
+ // Resize cursor was set by PanelGtk when mouse moved over window edge.
+ GdkCursor* cursor =
+ gdk_window_get_cursor(gtk_widget_get_window(GTK_WIDGET(window_)));
+ drag_helper_->InitialWindowEdgeMousePress(event, cursor, edge);
+ return TRUE;
+ }
+
+ return FALSE; // Continue to propagate the event.
+}
+
+void PanelGtk::ActiveWindowChanged(GdkWindow* active_window) {
+ // Do nothing if we're in the process of closing the browser window.
+ if (!window_)
+ return;
+
+ bool is_active = gtk_widget_get_window(GTK_WIDGET(window_)) == active_window;
+ if (is_active == is_active_)
+ return; // State did not change.
+
+ if (is_active) {
+ // If there's an app modal dialog (e.g., JS alert), try to redirect
+ // the user's attention to the window owning the dialog.
+ if (AppModalDialogQueue::GetInstance()->HasActiveDialog()) {
+ AppModalDialogQueue::GetInstance()->ActivateModalDialog();
+ return;
+ }
+ }
+
+ is_active_ = is_active;
+ titlebar_->UpdateTextColor();
+ InvalidateWindow();
+ panel_->OnActiveStateChanged(is_active_);
+}
+
+// Callback for the delete event. This event is fired when the user tries to
+// close the window.
+gboolean PanelGtk::OnMainWindowDeleteEvent(GtkWidget* widget,
+ GdkEvent* event) {
+ ClosePanel();
+
+ // Return true to prevent the gtk window from being destroyed. Close will
+ // destroy it for us.
+ return TRUE;
+}
+
+void PanelGtk::OnMainWindowDestroy(GtkWidget* widget) {
+ // BUG 8712. When we gtk_widget_destroy() in ClosePanel(), this will emit the
+ // signal right away, and we will be here (while ClosePanel() is still in the
+ // call stack). Let stack unwind before deleting the panel.
+ //
+ // We don't want to use DeleteSoon() here since it won't work on a nested pump
+ // (like in UI tests).
+ MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&base::DeletePointer<PanelGtk>, this));
+}
+
+void PanelGtk::ShowPanel() {
+ gtk_window_present(window_);
+ RevealPanel();
+}
+
+void PanelGtk::ShowPanelInactive() {
+ gtk_window_set_focus_on_map(window_, false);
+ gtk_widget_show(GTK_WIDGET(window_));
+ RevealPanel();
+}
+
+void PanelGtk::RevealPanel() {
+ DCHECK(!is_shown_);
+ is_shown_ = true;
+
+ // Grow the window from the botttom up to produce a 'reveal' animation.
+ int top = bounds_.bottom() - configure_size_.height();
+ StartBoundsAnimation(
+ gfx::Rect(bounds_.x(), top, bounds_.width(), configure_size_.height()),
+ bounds_);
+}
+
+gfx::Rect PanelGtk::GetPanelBounds() const {
+ return bounds_;
+}
+
+void PanelGtk::SetPanelBounds(const gfx::Rect& bounds) {
+ SetBoundsInternal(bounds, true);
+}
+
+void PanelGtk::SetPanelBoundsInstantly(const gfx::Rect& bounds) {
+ SetBoundsInternal(bounds, false);
+}
+
+void PanelGtk::SetBoundsInternal(const gfx::Rect& bounds, bool animate) {
+ if (bounds == bounds_)
+ return;
+
+ if (!animate) {
+ // If no animation is in progress, apply bounds change instantly. Otherwise,
+ // continue the animation with new target bounds.
+ if (!IsAnimatingBounds())
+ gdk_window_move_resize(gtk_widget_get_window(GTK_WIDGET(window_)),
+ bounds.x(), bounds.y(),
+ bounds.width(), bounds.height());
+ } else if (is_shown_) {
+ StartBoundsAnimation(bounds_, bounds);
+ }
+
+ bounds_ = bounds;
+}
+
+void PanelGtk::ClosePanel() {
+ // We're already closing. Do nothing.
+ if (!window_)
+ return;
+
+ if (!panel_->ShouldCloseWindow())
+ return;
+
+ if (bounds_animator_.get())
+ bounds_animator_.reset();
+
+ if (drag_helper_.get())
+ drag_helper_.reset();
+
+ if (accel_group_)
+ DisconnectAccelerators();
+
+ // Cancel any pending callback from the loading animation timer.
+ loading_animation_timer_.Stop();
+
+ if (panel_->GetWebContents()) {
+ // Hide the window (so it appears to have closed immediately).
+ // When web contents are destroyed, we will be called back again.
+ gtk_widget_hide(GTK_WIDGET(window_));
+ panel_->OnWindowClosing();
+ return;
+ }
+
+ GtkWidget* window = GTK_WIDGET(window_);
+ // To help catch bugs in any event handlers that might get fired during the
+ // destruction, set window_ to NULL before any handlers will run.
+ window_ = NULL;
+
+ panel_->OnNativePanelClosed();
+
+ // We don't want GlobalMenuBar handling any notifications or commands after
+ // the window is destroyed.
+ // TODO(jennb): global_menu_bar_->Disable();
+ gtk_widget_destroy(window);
+}
+
+void PanelGtk::ActivatePanel() {
+ gtk_window_present(window_);
+}
+
+void PanelGtk::DeactivatePanel() {
+ gdk_window_lower(gtk_widget_get_window(GTK_WIDGET(window_)));
+
+ // Per ICCCM: http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.7
+ // A convention is also required for clients that want to give up the
+ // input focus. There is no safe value set for them to set the input
+ // focus to; therefore, they should ignore input material.
+ //
+ // No way to deactive a GTK window. Pretend panel is deactivated
+ // and ignore input.
+ ActiveWindowChanged(NULL);
+}
+
+bool PanelGtk::IsPanelActive() const {
+ return is_active_;
+}
+
+void PanelGtk::PreventActivationByOS(bool prevent_activation) {
+ gtk_window_set_accept_focus(window_, !prevent_activation);
+}
+
+gfx::NativeWindow PanelGtk::GetNativePanelHandle() {
+ return window_;
+}
+
+void PanelGtk::UpdatePanelTitleBar() {
+ TRACE_EVENT0("ui::gtk", "PanelGtk::UpdatePanelTitleBar");
+ string16 title = panel_->GetWindowTitle();
+ gtk_window_set_title(window_, UTF16ToUTF8(title).c_str());
+ titlebar_->UpdateTitleAndIcon();
+}
+
+void PanelGtk::UpdatePanelLoadingAnimations(bool should_animate) {
+ if (should_animate) {
+ if (!loading_animation_timer_.IsRunning()) {
+ // Loads are happening, and the timer isn't running, so start it.
+ loading_animation_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kLoadingAnimationFrameTimeMs),
+ this,
+ &PanelGtk::LoadingAnimationCallback);
+ }
+ } else {
+ if (loading_animation_timer_.IsRunning()) {
+ loading_animation_timer_.Stop();
+ // Loads are now complete, update the state if a task was scheduled.
+ LoadingAnimationCallback();
+ }
+ }
+}
+
+void PanelGtk::LoadingAnimationCallback() {
+ titlebar_->UpdateThrobber(panel_->GetWebContents());
+}
+
+FindBar* PanelGtk::CreatePanelFindBar() {
+ return NULL; // legacy
+}
+
+void PanelGtk::NotifyPanelOnUserChangedTheme() {
+ titlebar_->UpdateTextColor();
+ InvalidateWindow();
+}
+
+void PanelGtk::PanelCut() {
+ gtk_window_util::DoCut(window_, panel_->GetWebContents());
+}
+
+void PanelGtk::PanelCopy() {
+ gtk_window_util::DoCopy(window_, panel_->GetWebContents());
+}
+
+void PanelGtk::PanelPaste() {
+ gtk_window_util::DoPaste(window_, panel_->GetWebContents());
+}
+
+void PanelGtk::DrawAttention(bool draw_attention) {
+ DCHECK((panel_->attention_mode() & Panel::USE_PANEL_ATTENTION) != 0);
+
+ if (is_drawing_attention_ == draw_attention)
+ return;
+
+ is_drawing_attention_ = draw_attention;
+
+ titlebar_->UpdateTextColor();
+ InvalidateWindow();
+
+ if ((panel_->attention_mode() & Panel::USE_SYSTEM_ATTENTION) != 0) {
+ // May not be respected by all window managers.
+ gtk_window_set_urgency_hint(window_, draw_attention);
+ }
+}
+
+bool PanelGtk::IsDrawingAttention() const {
+ return is_drawing_attention_;
+}
+
+bool PanelGtk::PreHandlePanelKeyboardEvent(
+ const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut) {
+ // No need to prehandle as no keys are reserved.
+ return false;
+}
+
+void PanelGtk::HandlePanelKeyboardEvent(
+ const NativeWebKeyboardEvent& event) {
+ GdkEventKey* os_event = &event.os_event->key;
+ if (os_event && event.type == WebKit::WebInputEvent::RawKeyDown)
+ gtk_window_activate_key(window_, os_event);
+}
+
+void PanelGtk::FullScreenModeChanged(bool is_full_screen) {
+ // Nothing to do here as z-order rules for panels ensures that they're below
+ // any app running in full screen mode.
+}
+
+void PanelGtk::PanelExpansionStateChanging(
+ Panel::ExpansionState old_state, Panel::ExpansionState new_state) {
+}
+
+void PanelGtk::AttachWebContents(content::WebContents* contents) {
+ if (!contents)
+ return;
+ gfx::NativeView widget = contents->GetNativeView();
+ if (widget) {
+ gtk_container_add(GTK_CONTAINER(contents_expanded_), widget);
+ gtk_widget_show(widget);
+ contents->WasShown();
+ }
+}
+
+void PanelGtk::DetachWebContents(content::WebContents* contents) {
+ gfx::NativeView widget = contents->GetNativeView();
+ if (widget) {
+ GtkWidget* parent = gtk_widget_get_parent(widget);
+ if (parent) {
+ DCHECK_EQ(parent, contents_expanded_);
+ gtk_container_remove(GTK_CONTAINER(contents_expanded_), widget);
+ }
+ }
+}
+
+Browser* PanelGtk::GetPanelBrowser() const {
+ return NULL; // legacy
+}
+
+gfx::Size PanelGtk::WindowSizeFromContentSize(
+ const gfx::Size& content_size) const {
+ return gfx::Size(content_size.width() + g_frame_size_.width(),
+ content_size.height() + g_frame_size_.height());
+}
+
+gfx::Size PanelGtk::ContentSizeFromWindowSize(
+ const gfx::Size& window_size) const {
+ return gfx::Size(window_size.width() - g_frame_size_.width(),
+ window_size.height() - g_frame_size_.height());
+}
+
+int PanelGtk::TitleOnlyHeight() const {
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(titlebar_->widget(), &allocation);
+ return allocation.height;
+}
+
+void PanelGtk::EnsurePanelFullyVisible() {
+ gtk_window_present(window_);
+}
+
+void PanelGtk::SetPanelAlwaysOnTop(bool on_top) {
+ gtk_window_set_keep_above(window_, on_top);
+}
+
+void PanelGtk::EnableResizeByMouse(bool enable) {
+}
+
+void PanelGtk::UpdatePanelMinimizeRestoreButtonVisibility() {
+ titlebar_->UpdateMinimizeRestoreButtonVisibility();
+}
+
+void PanelGtk::StartBoundsAnimation(
+ const gfx::Rect& from_bounds, const gfx::Rect& to_bounds) {
+ animation_start_bounds_ = IsAnimatingBounds() ?
+ last_animation_progressed_bounds_ : from_bounds;
+
+ bounds_animator_.reset(new PanelBoundsAnimation(
+ this, panel_.get(), animation_start_bounds_, to_bounds));
+
+ bounds_animator_->Start();
+ last_animation_progressed_bounds_ = animation_start_bounds_;
+}
+
+bool PanelGtk::IsAnimatingBounds() const {
+ return bounds_animator_.get() && bounds_animator_->is_animating();
+}
+
+void PanelGtk::AnimationEnded(const ui::Animation* animation) {
+ titlebar_->SendEnterNotifyToCloseButtonIfUnderMouse();
+ panel_->manager()->OnPanelAnimationEnded(panel_.get());
+}
+
+void PanelGtk::AnimationProgressed(const ui::Animation* animation) {
+ DCHECK(is_shown_);
+ gfx::Rect new_bounds = bounds_animator_->CurrentValueBetween(
+ animation_start_bounds_, bounds_);
+
+ gdk_window_move_resize(gtk_widget_get_window(GTK_WIDGET(window_)),
+ new_bounds.x(), new_bounds.y(),
+ new_bounds.width(), new_bounds.height());
+
+ last_animation_progressed_bounds_ = new_bounds;
+}
+
+gfx::Size PanelGtk::GetNonClientFrameSize() const {
+ GtkAllocation window_allocation;
+ gtk_widget_get_allocation(window_container_, &window_allocation);
+ GtkAllocation contents_allocation;
+ gtk_widget_get_allocation(contents_expanded_, &contents_allocation);
+ return gfx::Size(window_allocation.width - contents_allocation.width,
+ window_allocation.height - contents_allocation.height);
+}
+
+void PanelGtk::InvalidateWindow() {
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(GTK_WIDGET(window_), &allocation);
+ gdk_window_invalidate_rect(gtk_widget_get_window(GTK_WIDGET(window_)),
+ &allocation, TRUE);
+}
+
+// NativePanelTesting implementation.
+class GtkNativePanelTesting : public NativePanelTesting {
+ public:
+ explicit GtkNativePanelTesting(PanelGtk* panel_gtk);
+
+ private:
+ virtual void PressLeftMouseButtonTitlebar(
+ const gfx::Point& mouse_location, panel::ClickModifier modifier) OVERRIDE;
+ virtual void ReleaseMouseButtonTitlebar(
+ panel::ClickModifier modifier) OVERRIDE;
+ virtual void DragTitlebar(const gfx::Point& mouse_location) OVERRIDE;
+ virtual void CancelDragTitlebar() OVERRIDE;
+ virtual void FinishDragTitlebar() OVERRIDE;
+ virtual bool VerifyDrawingAttention() const OVERRIDE;
+ virtual bool VerifyActiveState(bool is_active) OVERRIDE;
+ virtual void WaitForWindowCreationToComplete() const OVERRIDE;
+ virtual bool IsWindowSizeKnown() const OVERRIDE;
+ virtual bool IsAnimatingBounds() const OVERRIDE;
+ virtual bool IsButtonVisible(
+ panel::TitlebarButtonType button_type) const OVERRIDE;
+
+ PanelGtk* panel_gtk_;
+};
+
+NativePanelTesting* PanelGtk::CreateNativePanelTesting() {
+ return new GtkNativePanelTesting(this);
+}
+
+GtkNativePanelTesting::GtkNativePanelTesting(PanelGtk* panel_gtk)
+ : panel_gtk_(panel_gtk) {
+}
+
+void GtkNativePanelTesting::PressLeftMouseButtonTitlebar(
+ const gfx::Point& mouse_location, panel::ClickModifier modifier) {
+ // If there is an animation, wait for it to finish as we don't handle button
+ // clicks while animation is in progress.
+ while (panel_gtk_->IsAnimatingBounds())
+ MessageLoopForUI::current()->RunAllPending();
+
+ GdkEvent* event = gdk_event_new(GDK_BUTTON_PRESS);
+ event->button.button = 1;
+ event->button.x_root = mouse_location.x();
+ event->button.y_root = mouse_location.y();
+ if (modifier == panel::APPLY_TO_ALL)
+ event->button.state |= GDK_CONTROL_MASK;
+ panel_gtk_->OnTitlebarButtonPressEvent(
+ NULL, reinterpret_cast<GdkEventButton*>(event));
+ gdk_event_free(event);
+ MessageLoopForUI::current()->RunAllPending();
+}
+
+void GtkNativePanelTesting::ReleaseMouseButtonTitlebar(
+ panel::ClickModifier modifier) {
+ GdkEvent* event = gdk_event_new(GDK_BUTTON_RELEASE);
+ event->button.button = 1;
+ if (modifier == panel::APPLY_TO_ALL)
+ event->button.state |= GDK_CONTROL_MASK;
+ if (panel_gtk_->drag_helper_.get()) {
+ panel_gtk_->drag_helper_->OnButtonReleaseEvent(
+ NULL, reinterpret_cast<GdkEventButton*>(event));
+ } else {
+ panel_gtk_->OnTitlebarButtonReleaseEvent(
+ NULL, reinterpret_cast<GdkEventButton*>(event));
+ }
+ gdk_event_free(event);
+ MessageLoopForUI::current()->RunAllPending();
+}
+
+void GtkNativePanelTesting::DragTitlebar(const gfx::Point& mouse_location) {
+ if (!panel_gtk_->drag_helper_.get())
+ return;
+ GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY);
+ event->motion.x_root = mouse_location.x();
+ event->motion.y_root = mouse_location.y();
+ panel_gtk_->drag_helper_->OnMouseMoveEvent(
+ NULL, reinterpret_cast<GdkEventMotion*>(event));
+ gdk_event_free(event);
+ MessageLoopForUI::current()->RunAllPending();
+}
+
+void GtkNativePanelTesting::CancelDragTitlebar() {
+ if (!panel_gtk_->drag_helper_.get())
+ return;
+ panel_gtk_->drag_helper_->OnGrabBrokenEvent(NULL, NULL);
+ MessageLoopForUI::current()->RunAllPending();
+}
+
+void GtkNativePanelTesting::FinishDragTitlebar() {
+ if (!panel_gtk_->drag_helper_.get())
+ return;
+ ReleaseMouseButtonTitlebar(panel::NO_MODIFIER);
+}
+
+bool GtkNativePanelTesting::VerifyDrawingAttention() const {
+ return panel_gtk_->IsDrawingAttention();
+}
+
+bool GtkNativePanelTesting::VerifyActiveState(bool is_active) {
+ // TODO(jianli): to be implemented. http://crbug.com/102737
+ return false;
+}
+
+void GtkNativePanelTesting::WaitForWindowCreationToComplete() const {
+ while (g_frame_size_.IsEmpty())
+ MessageLoopForUI::current()->RunAllPending();
+ while (panel_gtk_->IsAnimatingBounds())
+ MessageLoopForUI::current()->RunAllPending();
+}
+
+bool GtkNativePanelTesting::IsWindowSizeKnown() const {
+ return !g_frame_size_.IsEmpty();
+}
+
+bool GtkNativePanelTesting::IsAnimatingBounds() const {
+ return panel_gtk_->IsAnimatingBounds();
+}
+
+bool GtkNativePanelTesting::IsButtonVisible(
+ panel::TitlebarButtonType button_type) const {
+ PanelTitlebarGtk* titlebar = panel_gtk_->titlebar();
+ CustomDrawButton* button;
+ switch (button_type) {
+ case panel::CLOSE_BUTTON:
+ button = titlebar->close_button();
+ break;
+ case panel::MINIMIZE_BUTTON:
+ button = titlebar->minimize_button();
+ break;
+ case panel::RESTORE_BUTTON:
+ button = titlebar->restore_button();
+ break;
+ default:
+ NOTREACHED();
+ return false;
+ }
+ return gtk_widget_get_visible(button->widget());
}