| Index: chrome/browser/ui/panels/panel_gtk.cc
|
| diff --git a/chrome/browser/ui/panels/panel_gtk.cc b/chrome/browser/ui/panels/panel_gtk.cc
|
| index eeb603958f20f5572363698a3e2f39b878e576aa..f57183f58885475f18611c49d460c94464e44caf 100644
|
| --- a/chrome/browser/ui/panels/panel_gtk.cc
|
| +++ b/chrome/browser/ui/panels/panel_gtk.cc
|
| @@ -2,11 +2,1179 @@
|
| // 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() {
|
| + static AcceleratorGtkMap* accelerator_table = NULL;
|
| + if (!accelerator_table) {
|
| + accelerator_table = new AcceleratorGtkMap();
|
| + 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;
|
| +}
|
| +
|
| +}
|
|
|
| // 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;
|
| +}
|
| +
|
| +// static
|
| +gfx::Size PanelGtk::frame_size_;
|
| +
|
| +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_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 (!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).
|
| + 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_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_util::DoCut(window_, panel_->GetWebContents());
|
| +}
|
| +
|
| +void PanelGtk::PanelCopy() {
|
| + gtk_util::DoCopy(window_, panel_->GetWebContents());
|
| +}
|
| +
|
| +void PanelGtk::PanelPaste() {
|
| + gtk_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() + frame_size_.width(),
|
| + content_size.height() + frame_size_.height());
|
| +}
|
| +
|
| +gfx::Size PanelGtk::ContentSizeFromWindowSize(
|
| + const gfx::Size& window_size) const {
|
| + return gfx::Size(window_size.width() - frame_size_.width(),
|
| + window_size.height() - 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 (panel_gtk_->frame_size_.IsEmpty())
|
| + MessageLoopForUI::current()->RunAllPending();
|
| + while (panel_gtk_->IsAnimatingBounds())
|
| + MessageLoopForUI::current()->RunAllPending();
|
| +}
|
| +
|
| +bool GtkNativePanelTesting::IsWindowSizeKnown() const {
|
| + return !panel_gtk_->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());
|
| }
|
|
|