diff options
author | tfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-21 17:18:14 +0000 |
---|---|---|
committer | tfarina@chromium.org <tfarina@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-11-21 17:18:14 +0000 |
commit | d57afa518d4ad5192b5a2d0f7227b2ddebd21ab9 (patch) | |
tree | 0f26ebcbcb4708db3b4520ba46929bc6a3727d1d /ui/views/widget/native_widget_gtk.cc | |
parent | c999f81499325506e3be3e373b1637bc3d71e85e (diff) | |
download | chromium_src-d57afa518d4ad5192b5a2d0f7227b2ddebd21ab9.zip chromium_src-d57afa518d4ad5192b5a2d0f7227b2ddebd21ab9.tar.gz chromium_src-d57afa518d4ad5192b5a2d0f7227b2ddebd21ab9.tar.bz2 |
views: Move widget/ directory to ui/views.
BUG=104039
R=ben@chromium.org
Review URL: http://codereview.chromium.org/8598031
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@110949 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui/views/widget/native_widget_gtk.cc')
-rw-r--r-- | ui/views/widget/native_widget_gtk.cc | 2317 |
1 files changed, 2317 insertions, 0 deletions
diff --git a/ui/views/widget/native_widget_gtk.cc b/ui/views/widget/native_widget_gtk.cc new file mode 100644 index 0000000..8da0f51 --- /dev/null +++ b/ui/views/widget/native_widget_gtk.cc @@ -0,0 +1,2317 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/views/widget/native_widget_gtk.h" + +#include <X11/Xatom.h> +#include <X11/Xlib.h> +#include <X11/extensions/shape.h> +#include <gdk/gdk.h> +#include <gdk/gdkx.h> + +#include <set> +#include <vector> + +#include "base/auto_reset.h" +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/message_loop.h" +#include "base/utf_string_conversions.h" +#include "ui/base/dragdrop/drag_drop_types.h" +#include "ui/base/dragdrop/os_exchange_data.h" +#include "ui/base/dragdrop/os_exchange_data_provider_gtk.h" +#include "ui/base/gtk/g_object_destructor_filo.h" +#include "ui/base/gtk/gtk_signal_registrar.h" +#include "ui/base/gtk/gtk_windowing.h" +#include "ui/base/gtk/scoped_handle_gtk.h" +#include "ui/base/hit_test.h" +#include "ui/base/x/x11_util.h" +#include "ui/gfx/canvas_skia_paint.h" +#include "ui/gfx/compositor/compositor.h" +#include "ui/gfx/gtk_util.h" +#include "ui/gfx/path.h" +#include "ui/gfx/screen.h" +#include "ui/views/bubble/bubble_delegate.h" +#include "ui/views/focus/view_storage.h" +#include "ui/views/ime/input_method_gtk.h" +#include "ui/views/widget/drop_target_gtk.h" +#include "ui/views/widget/drop_target_gtk.h" +#include "ui/views/widget/gtk_views_fixed.h" +#include "ui/views/widget/gtk_views_fixed.h" +#include "ui/views/widget/gtk_views_window.h" +#include "ui/views/widget/gtk_views_window.h" +#include "ui/views/widget/root_view.h" +#include "ui/views/widget/root_view.h" +#include "ui/views/widget/widget_delegate.h" +#include "ui/views/widget/widget_delegate.h" +#include "views/controls/textfield/native_textfield_views.h" +#include "views/views_delegate.h" + +#if defined(TOUCH_UI) +#include "ui/base/touch/touch_factory.h" +#include "ui/views/widget/tooltip_manager_views.h" +#else +#include "ui/views/widget/tooltip_manager_gtk.h" +#endif + +#if defined(HAVE_IBUS) +#include "ui/views/ime/input_method_ibus.h" +#endif + +using ui::OSExchangeData; +using ui::OSExchangeDataProviderGtk; +using ui::ActiveWindowWatcherX; + +namespace views { + +namespace { + +// Links the GtkWidget to its NativeWidget. +const char* const kNativeWidgetKey = "__VIEWS_NATIVE_WIDGET__"; + +// A g_object data key to associate a CompositePainter object to a GtkWidget. +const char* kCompositePainterKey = "__VIEWS_COMPOSITE_PAINTER__"; + +// A g_object data key to associate the flag whether or not the widget +// is composited to a GtkWidget. gtk_widget_is_composited simply tells +// if x11 supports composition and cannot be used to tell if given widget +// is composited. +const char* kCompositeEnabledKey = "__VIEWS_COMPOSITE_ENABLED__"; + +// A g_object data key to associate the expose handler id that is +// used to remove FREEZE_UPDATE property on the window. +const char* kExposeHandlerIdKey = "__VIEWS_EXPOSE_HANDLER_ID__"; + +// CompositePainter draws a composited child widgets image into its +// drawing area. This object is created at most once for a widget and kept +// until the widget is destroyed. +class CompositePainter { + public: + explicit CompositePainter(GtkWidget* parent) + : parent_object_(G_OBJECT(parent)) { + handler_id_ = g_signal_connect_after( + parent_object_, "expose_event", G_CALLBACK(OnCompositePaint), NULL); + } + + static void AddCompositePainter(GtkWidget* widget) { + CompositePainter* painter = static_cast<CompositePainter*>( + g_object_get_data(G_OBJECT(widget), kCompositePainterKey)); + if (!painter) { + g_object_set_data(G_OBJECT(widget), kCompositePainterKey, + new CompositePainter(widget)); + g_signal_connect(widget, "destroy", + G_CALLBACK(&DestroyPainter), NULL); + } + } + + // Set the composition flag. + static void SetComposited(GtkWidget* widget) { + g_object_set_data(G_OBJECT(widget), kCompositeEnabledKey, + const_cast<char*>("")); + } + + // Returns true if the |widget| is composited and ready to be drawn. + static bool IsComposited(GtkWidget* widget) { + return g_object_get_data(G_OBJECT(widget), kCompositeEnabledKey) != NULL; + } + + private: + virtual ~CompositePainter() {} + + // Composes a image from one child. + static void CompositeChildWidget(GtkWidget* child, gpointer data) { + GdkEventExpose* event = static_cast<GdkEventExpose*>(data); + GtkWidget* parent = gtk_widget_get_parent(child); + DCHECK(parent); + if (IsComposited(child)) { + cairo_t* cr = gdk_cairo_create(parent->window); + gdk_cairo_set_source_pixmap(cr, child->window, + child->allocation.x, + child->allocation.y); + GdkRegion* region = gdk_region_rectangle(&child->allocation); + gdk_region_intersect(region, event->region); + gdk_cairo_region(cr, region); + cairo_clip(cr); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_paint(cr); + cairo_destroy(cr); + } + } + + // Expose-event handler that compose & draws children's image into + // the |parent|'s drawing area. + static gboolean OnCompositePaint(GtkWidget* parent, GdkEventExpose* event) { + gtk_container_foreach(GTK_CONTAINER(parent), + CompositeChildWidget, + event); + return false; + } + + static void DestroyPainter(GtkWidget* object) { + CompositePainter* painter = reinterpret_cast<CompositePainter*>( + g_object_get_data(G_OBJECT(object), kCompositePainterKey)); + DCHECK(painter); + delete painter; + } + + GObject* parent_object_; + gulong handler_id_; + + DISALLOW_COPY_AND_ASSIGN(CompositePainter); +}; + +void EnumerateChildWidgetsForNativeWidgets(GtkWidget* child_widget, + gpointer param) { + // Walk child widgets, if necessary. + if (GTK_IS_CONTAINER(child_widget)) { + gtk_container_foreach(GTK_CONTAINER(child_widget), + EnumerateChildWidgetsForNativeWidgets, + param); + } + + Widget* widget = Widget::GetWidgetForNativeView(child_widget); + if (widget) { + Widget::Widgets* widgets = reinterpret_cast<Widget::Widgets*>(param); + widgets->insert(widget); + } +} + +void RemoveExposeHandlerIfExists(GtkWidget* widget) { + gulong id = reinterpret_cast<gulong>(g_object_get_data(G_OBJECT(widget), + kExposeHandlerIdKey)); + if (id) { + g_signal_handler_disconnect(G_OBJECT(widget), id); + g_object_set_data(G_OBJECT(widget), kExposeHandlerIdKey, 0); + } +} + +GtkWindowType WindowTypeToGtkWindowType(Widget::InitParams::Type type) { + switch (type) { + case Widget::InitParams::TYPE_BUBBLE: + case Widget::InitParams::TYPE_WINDOW: + case Widget::InitParams::TYPE_WINDOW_FRAMELESS: + return GTK_WINDOW_TOPLEVEL; + default: + return GTK_WINDOW_POPUP; + } + NOTREACHED(); + return GTK_WINDOW_TOPLEVEL; +} + +// Converts a Windows-style hit test result code into a GDK window edge. +GdkWindowEdge HitTestCodeToGDKWindowEdge(int hittest_code) { + switch (hittest_code) { + case HTBOTTOM: + return GDK_WINDOW_EDGE_SOUTH; + case HTBOTTOMLEFT: + return GDK_WINDOW_EDGE_SOUTH_WEST; + case HTBOTTOMRIGHT: + case HTGROWBOX: + return GDK_WINDOW_EDGE_SOUTH_EAST; + case HTLEFT: + return GDK_WINDOW_EDGE_WEST; + case HTRIGHT: + return GDK_WINDOW_EDGE_EAST; + case HTTOP: + return GDK_WINDOW_EDGE_NORTH; + case HTTOPLEFT: + return GDK_WINDOW_EDGE_NORTH_WEST; + case HTTOPRIGHT: + return GDK_WINDOW_EDGE_NORTH_EAST; + default: + NOTREACHED(); + break; + } + // Default to something defaultish. + return HitTestCodeToGDKWindowEdge(HTGROWBOX); +} + +// Converts a Windows-style hit test result code into a GDK cursor type. +GdkCursorType HitTestCodeToGdkCursorType(int hittest_code) { + switch (hittest_code) { + case HTBOTTOM: + return GDK_BOTTOM_SIDE; + case HTBOTTOMLEFT: + return GDK_BOTTOM_LEFT_CORNER; + case HTBOTTOMRIGHT: + case HTGROWBOX: + return GDK_BOTTOM_RIGHT_CORNER; + case HTLEFT: + return GDK_LEFT_SIDE; + case HTRIGHT: + return GDK_RIGHT_SIDE; + case HTTOP: + return GDK_TOP_SIDE; + case HTTOPLEFT: + return GDK_TOP_LEFT_CORNER; + case HTTOPRIGHT: + return GDK_TOP_RIGHT_CORNER; + default: + break; + } + // Default to something defaultish. + return GDK_LEFT_PTR; +} + +} // namespace + +// During drag and drop GTK sends a drag-leave during a drop. This means we +// have no way to tell the difference between a normal drag leave and a drop. +// To work around that we listen for DROP_START, then ignore the subsequent +// drag-leave that GTK generates. +class NativeWidgetGtk::DropObserver : public MessageLoopForUI::Observer { + public: + DropObserver() {} + + static DropObserver* GetInstance() { + return Singleton<DropObserver>::get(); + } +#if defined(TOUCH_UI) + virtual base::EventStatus WillProcessEvent( + const base::NativeEvent& event) OVERRIDE { + return base::EVENT_CONTINUE; + } + + virtual void DidProcessEvent(const base::NativeEvent& event) OVERRIDE { + } +#else + virtual void WillProcessEvent(GdkEvent* event) { + if (event->type == GDK_DROP_START) { + NativeWidgetGtk* widget = GetNativeWidgetGtkForEvent(event); + if (widget) + widget->ignore_drag_leave_ = true; + } + } + + virtual void DidProcessEvent(GdkEvent* event) { + } +#endif + + private: + NativeWidgetGtk* GetNativeWidgetGtkForEvent(GdkEvent* event) { + GtkWidget* gtk_widget = gtk_get_event_widget(event); + if (!gtk_widget) + return NULL; + + return static_cast<NativeWidgetGtk*>( + internal::NativeWidgetPrivate::GetNativeWidgetForNativeView( + gtk_widget)); + } + + DISALLOW_COPY_AND_ASSIGN(DropObserver); +}; + +// Returns the position of a widget on screen. +static void GetWidgetPositionOnScreen(GtkWidget* widget, int* x, int *y) { + // First get the root window. + GtkWidget* root = widget; + while (root && !GTK_IS_WINDOW(root)) { + root = gtk_widget_get_parent(root); + } + if (!root) { + // If root is null we're not parented. Return 0x0 and assume the caller will + // query again when we're parented. + *x = *y = 0; + return; + } + // Translate the coordinate from widget to root window. + gtk_widget_translate_coordinates(widget, root, 0, 0, x, y); + // Then adjust the position with the position of the root window. + int window_x, window_y; + gtk_window_get_position(GTK_WINDOW(root), &window_x, &window_y); + *x += window_x; + *y += window_y; +} + +// "expose-event" handler of drag icon widget that renders drag image pixbuf. +static gboolean DragIconWidgetPaint(GtkWidget* widget, + GdkEventExpose* event, + gpointer data) { + GdkPixbuf* pixbuf = reinterpret_cast<GdkPixbuf*>(data); + + cairo_t* cr = gdk_cairo_create(widget->window); + + gdk_cairo_region(cr, event->region); + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + gdk_cairo_set_source_pixbuf(cr, pixbuf, 0.0, 0.0); + cairo_paint(cr); + + cairo_destroy(cr); + return true; +} + +// Creates a drag icon widget that draws drag_image. +static GtkWidget* CreateDragIconWidget(GdkPixbuf* drag_image) { + GdkColormap* rgba_colormap = + gdk_screen_get_rgba_colormap(gdk_screen_get_default()); + if (!rgba_colormap) + return NULL; + + GtkWidget* drag_widget = gtk_window_new(GTK_WINDOW_POPUP); + + gtk_widget_set_colormap(drag_widget, rgba_colormap); + gtk_widget_set_app_paintable(drag_widget, true); + gtk_widget_set_size_request(drag_widget, + gdk_pixbuf_get_width(drag_image), + gdk_pixbuf_get_height(drag_image)); + + g_signal_connect(G_OBJECT(drag_widget), "expose-event", + G_CALLBACK(&DragIconWidgetPaint), drag_image); + return drag_widget; +} + +// static +GtkWidget* NativeWidgetGtk::null_parent_ = NULL; + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetGtk, public: + +NativeWidgetGtk::NativeWidgetGtk(internal::NativeWidgetDelegate* delegate) + : delegate_(delegate), + widget_(NULL), + window_contents_(NULL), + child_(false), + ALLOW_THIS_IN_INITIALIZER_LIST(close_widget_factory_(this)), + ownership_(Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET), + transparent_(false), + ignore_events_(false), + ignore_drag_leave_(false), + opacity_(255), + drag_data_(NULL), + window_state_(GDK_WINDOW_STATE_WITHDRAWN), + is_active_(false), + transient_to_parent_(false), + got_initial_focus_in_(false), + has_focus_(false), + always_on_top_(false), + is_double_buffered_(false), + should_handle_menu_key_release_(false), + dragged_view_(NULL), + painted_(false), + has_pointer_grab_(false), + has_keyboard_grab_(false), + grab_notify_signal_id_(0), + is_menu_(false), + signal_registrar_(new ui::GtkSignalRegistrar) { +#if defined(TOUCH_UI) + // Make sure the touch factory is initialized so that it can setup XInput2 for + // the widget. + ui::TouchFactory::GetInstance(); +#endif + static bool installed_message_loop_observer = false; + if (!installed_message_loop_observer) { + installed_message_loop_observer = true; + MessageLoopForUI* loop = MessageLoopForUI::current(); + if (loop) + loop->AddObserver(DropObserver::GetInstance()); + } +} + +NativeWidgetGtk::~NativeWidgetGtk() { + // We need to delete the input method before calling DestroyRootView(), + // because it'll set focus_manager_ to NULL. + if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) { + DCHECK(widget_ == NULL); + delete delegate_; + } else { + // Disconnect from GObjectDestructorFILO because we're + // deleting the NativeWidgetGtk. + bool has_widget = !!widget_; + if (has_widget) + ui::GObjectDestructorFILO::GetInstance()->Disconnect( + G_OBJECT(widget_), &OnDestroyedThunk, this); + CloseNow(); + // Call OnNativeWidgetDestroyed because we're not calling + // OnDestroyedThunk + if (has_widget) + delegate_->OnNativeWidgetDestroyed(); + } +} + +GtkWindow* NativeWidgetGtk::GetTransientParent() const { + return (!child_ && widget_) ? + gtk_window_get_transient_for(GTK_WINDOW(widget_)) : NULL; +} + +bool NativeWidgetGtk::MakeTransparent() { + // Transparency can only be enabled only if we haven't realized the widget. + DCHECK(!widget_); + + if (!gdk_screen_is_composited(gdk_screen_get_default())) { + // Transparency is only supported for compositing window managers. + // NOTE: there's a race during ChromeOS startup such that X might think + // compositing isn't supported. We ignore it if the wm says compositing + // isn't supported. + DLOG(WARNING) << "compositing not supported; allowing anyway"; + } + + if (!gdk_screen_get_rgba_colormap(gdk_screen_get_default())) { + // We need rgba to make the window transparent. + return false; + } + + transparent_ = true; + return true; +} + +void NativeWidgetGtk::EnableDoubleBuffer(bool enabled) { + is_double_buffered_ = enabled; + if (window_contents_) { + if (is_double_buffered_) + GTK_WIDGET_SET_FLAGS(window_contents_, GTK_DOUBLE_BUFFERED); + else + GTK_WIDGET_UNSET_FLAGS(window_contents_, GTK_DOUBLE_BUFFERED); + } +} + +void NativeWidgetGtk::AddChild(GtkWidget* child) { + gtk_container_add(GTK_CONTAINER(window_contents_), child); +} + +void NativeWidgetGtk::RemoveChild(GtkWidget* child) { + // We can be called after the contents widget has been destroyed, e.g. any + // NativeViewHost not removed from the view hierarchy before the window is + // closed. + if (GTK_IS_CONTAINER(window_contents_)) { + gtk_container_remove(GTK_CONTAINER(window_contents_), child); + gtk_views_fixed_set_widget_size(child, 0, 0); + } +} + +void NativeWidgetGtk::ReparentChild(GtkWidget* child) { + gtk_widget_reparent(child, window_contents_); +} + +void NativeWidgetGtk::PositionChild(GtkWidget* child, int x, int y, int w, + int h) { + gtk_views_fixed_set_widget_size(child, w, h); + gtk_fixed_move(GTK_FIXED(window_contents_), child, x, y); +} + +void NativeWidgetGtk::DoDrag(const OSExchangeData& data, int operation) { + const OSExchangeDataProviderGtk& data_provider = + static_cast<const OSExchangeDataProviderGtk&>(data.provider()); + GtkTargetList* targets = data_provider.GetTargetList(); + GdkEvent* current_event = gtk_get_current_event(); + const OSExchangeDataProviderGtk& provider( + static_cast<const OSExchangeDataProviderGtk&>(data.provider())); + + GdkDragContext* context = gtk_drag_begin( + window_contents_, + targets, + static_cast<GdkDragAction>( + ui::DragDropTypes::DragOperationToGdkDragAction(operation)), + 1, + current_event); + + GtkWidget* drag_icon_widget = NULL; + + // Set the drag image if one was supplied. + if (provider.drag_image()) { + drag_icon_widget = CreateDragIconWidget(provider.drag_image()); + if (drag_icon_widget) { + // Use a widget as the drag icon when compositing is enabled for proper + // transparency handling. + g_object_ref(provider.drag_image()); + gtk_drag_set_icon_widget(context, + drag_icon_widget, + provider.cursor_offset().x(), + provider.cursor_offset().y()); + } else { + gtk_drag_set_icon_pixbuf(context, + provider.drag_image(), + provider.cursor_offset().x(), + provider.cursor_offset().y()); + } + } + + if (current_event) + gdk_event_free(current_event); + gtk_target_list_unref(targets); + + drag_data_ = &data_provider; + + // Block the caller until drag is done by running a nested message loop. + MessageLoopForUI::current()->RunWithDispatcher(NULL); + + drag_data_ = NULL; + + if (drag_icon_widget) { + gtk_widget_destroy(drag_icon_widget); + g_object_unref(provider.drag_image()); + } +} + +void NativeWidgetGtk::OnActiveChanged() { + delegate_->OnNativeWidgetActivationChanged(IsActive()); +} + +void NativeWidgetGtk::ResetDropTarget() { + ignore_drag_leave_ = false; + drop_target_.reset(NULL); +} + +void NativeWidgetGtk::GetRequestedSize(gfx::Size* out) const { + int width, height; + if (GTK_IS_VIEWS_FIXED(widget_) && + gtk_views_fixed_get_widget_size(GetNativeView(), &width, &height)) { + out->SetSize(width, height); + } else { + GtkRequisition requisition; + gtk_widget_get_child_requisition(GetNativeView(), &requisition); + out->SetSize(requisition.width, requisition.height); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetGtk, ActiveWindowWatcherX::Observer implementation: + +void NativeWidgetGtk::ActiveWindowChanged(GdkWindow* active_window) { + if (!GetNativeView()) + return; + + bool was_active = IsActive(); + is_active_ = (active_window == GTK_WIDGET(GetNativeView())->window); + if (!is_active_ && active_window && !child_) { + // We're not active, but the force the window to be rendered as active if + // a child window is transient to us. + gpointer data = NULL; + gdk_window_get_user_data(active_window, &data); + GtkWidget* widget = reinterpret_cast<GtkWidget*>(data); + is_active_ = + (widget && GTK_IS_WINDOW(widget) && + gtk_window_get_transient_for(GTK_WINDOW(widget)) == GTK_WINDOW( + widget_)); + } + if (was_active != IsActive()) { + OnActiveChanged(); + GetWidget()->GetRootView()->SchedulePaint(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetGtk implementation: + +bool NativeWidgetGtk::HandleKeyboardEvent(const KeyEvent& key) { + if (!GetWidget()->GetFocusManager()) + return false; + + const int key_code = key.key_code(); + bool handled = false; + + // Always reset |should_handle_menu_key_release_| unless we are handling a + // VKEY_MENU key release event. It ensures that VKEY_MENU accelerator can only + // be activated when handling a VKEY_MENU key release event which is preceded + // by an un-handled VKEY_MENU key press event. + if (key_code != ui::VKEY_MENU || key.type() != ui::ET_KEY_RELEASED) + should_handle_menu_key_release_ = false; + + if (key.type() == ui::ET_KEY_PRESSED) { + // VKEY_MENU is triggered by key release event. + // FocusManager::OnKeyEvent() returns false when the key has been consumed. + if (key_code != ui::VKEY_MENU) + handled = !GetWidget()->GetFocusManager()->OnKeyEvent(key); + else + should_handle_menu_key_release_ = true; + } else if (key_code == ui::VKEY_MENU && should_handle_menu_key_release_ && + (key.flags() & ~ui::EF_ALT_DOWN) == 0) { + // Trigger VKEY_MENU when only this key is pressed and released, and both + // press and release events are not handled by others. + ui::Accelerator accelerator(ui::VKEY_MENU, false, false, false); + handled = GetWidget()->GetFocusManager()->ProcessAccelerator(accelerator); + } + + return handled; +} + +bool NativeWidgetGtk::SuppressFreezeUpdates() { + if (!painted_) { + painted_ = true; + return true; + } + return false; +} + +// static +void NativeWidgetGtk::UpdateFreezeUpdatesProperty(GtkWindow* window, + bool enable) { + if (!GTK_WIDGET_REALIZED(GTK_WIDGET(window))) + gtk_widget_realize(GTK_WIDGET(window)); + GdkWindow* gdk_window = GTK_WIDGET(window)->window; + + static GdkAtom freeze_atom_ = + gdk_atom_intern("_CHROME_FREEZE_UPDATES", FALSE); + if (enable) { + VLOG(1) << "setting FREEZE UPDATES property. xid=" << + GDK_WINDOW_XID(gdk_window); + int32 val = 1; + gdk_property_change(gdk_window, + freeze_atom_, + freeze_atom_, + 32, + GDK_PROP_MODE_REPLACE, + reinterpret_cast<const guchar*>(&val), + 1); + } else { + VLOG(1) << "deleting FREEZE UPDATES property. xid=" << + GDK_WINDOW_XID(gdk_window); + gdk_property_delete(gdk_window, freeze_atom_); + } +} + +// static +void NativeWidgetGtk::RegisterChildExposeHandler(GtkWidget* child) { + RemoveExposeHandlerIfExists(child); + gulong id = g_signal_connect_after(child, "expose-event", + G_CALLBACK(&ChildExposeHandler), NULL); + g_object_set_data(G_OBJECT(child), kExposeHandlerIdKey, + reinterpret_cast<void*>(id)); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetGtk, NativeWidget implementation: + +void NativeWidgetGtk::InitNativeWidget(const Widget::InitParams& params) { + SetInitParams(params); + + Widget::InitParams modified_params = params; + if (params.parent_widget) { + NativeWidgetGtk* parent_gtk = + static_cast<NativeWidgetGtk*>(params.parent_widget->native_widget()); + modified_params.parent = child_ ? parent_gtk->window_contents() + : params.parent_widget->GetNativeView(); + } + + if (!child_) + ActiveWindowWatcherX::AddObserver(this); + + // Make container here. + CreateGtkWidget(modified_params); + + if (params.type == Widget::InitParams::TYPE_MENU) { + gtk_window_set_destroy_with_parent(GTK_WINDOW(GetNativeView()), TRUE); + gtk_window_set_type_hint(GTK_WINDOW(GetNativeView()), + GDK_WINDOW_TYPE_HINT_MENU); + } + + if (View::get_use_acceleration_when_possible()) { + if (ui::Compositor::compositor_factory()) { + compositor_ = (*ui::Compositor::compositor_factory())(this); + } else { + gint width, height; + gdk_drawable_get_size(window_contents_->window, &width, &height); + compositor_ = ui::Compositor::Create(this, + GDK_WINDOW_XID(window_contents_->window), + gfx::Size(width, height)); + } + if (compositor_.get()) { + View* root_view = delegate_->AsWidget()->GetRootView(); + root_view->SetPaintToLayer(true); + compositor_->SetRootLayer(root_view->layer()); + root_view->SetFillsBoundsOpaquely(!transparent_); + } + } + + delegate_->OnNativeWidgetCreated(); + + if (opacity_ != 255) + SetOpacity(opacity_); + + // Make sure we receive our motion events. + + // In general we register most events on the parent of all widgets. At a + // minimum we need painting to happen on the parent (otherwise painting + // doesn't work at all), and similarly we need mouse release events on the + // parent as windows don't get mouse releases. + gtk_widget_add_events(window_contents_, + GDK_ENTER_NOTIFY_MASK | + GDK_LEAVE_NOTIFY_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK | + GDK_KEY_PRESS_MASK | + GDK_KEY_RELEASE_MASK); + + signal_registrar_->ConnectAfter(G_OBJECT(window_contents_), "size_request", + G_CALLBACK(&OnSizeRequestThunk), this); + signal_registrar_->ConnectAfter(G_OBJECT(window_contents_), "size_allocate", + G_CALLBACK(&OnSizeAllocateThunk), this); + gtk_widget_set_app_paintable(window_contents_, true); + signal_registrar_->Connect(window_contents_, "expose_event", + G_CALLBACK(&OnPaintThunk), this); + signal_registrar_->Connect(window_contents_, "enter_notify_event", + G_CALLBACK(&OnEnterNotifyThunk), this); + signal_registrar_->Connect(window_contents_, "leave_notify_event", + G_CALLBACK(&OnLeaveNotifyThunk), this); + signal_registrar_->Connect(window_contents_, "motion_notify_event", + G_CALLBACK(&OnMotionNotifyThunk), this); + signal_registrar_->Connect(window_contents_, "button_press_event", + G_CALLBACK(&OnButtonPressThunk), this); + signal_registrar_->Connect(window_contents_, "button_release_event", + G_CALLBACK(&OnButtonReleaseThunk), this); + signal_registrar_->Connect(window_contents_, "grab_broken_event", + G_CALLBACK(&OnGrabBrokeEventThunk), this); + signal_registrar_->Connect(window_contents_, "scroll_event", + G_CALLBACK(&OnScrollThunk), this); + signal_registrar_->Connect(window_contents_, "visibility_notify_event", + G_CALLBACK(&OnVisibilityNotifyThunk), this); + + // In order to receive notification when the window is no longer the front + // window, we need to install these on the widget. + // NOTE: this doesn't work with focus follows mouse. + signal_registrar_->Connect(widget_, "focus_in_event", + G_CALLBACK(&OnFocusInThunk), this); + signal_registrar_->Connect(widget_, "focus_out_event", + G_CALLBACK(&OnFocusOutThunk), this); + signal_registrar_->Connect(widget_, "destroy", + G_CALLBACK(&OnDestroyThunk), this); + signal_registrar_->Connect(widget_, "show", + G_CALLBACK(&OnShowThunk), this); + signal_registrar_->Connect(widget_, "map", + G_CALLBACK(&OnMapThunk), this); + signal_registrar_->Connect(widget_, "hide", + G_CALLBACK(&OnHideThunk), this); + signal_registrar_->Connect(widget_, "configure-event", + G_CALLBACK(&OnConfigureEventThunk), this); + + // Views/FocusManager (re)sets the focus to the root window, + // so we need to connect signal handlers to the gtk window. + // See views::Views::Focus and views::FocusManager::ClearNativeFocus + // for more details. + signal_registrar_->Connect(widget_, "key_press_event", + G_CALLBACK(&OnEventKeyThunk), this); + signal_registrar_->Connect(widget_, "key_release_event", + G_CALLBACK(&OnEventKeyThunk), this); + + // Drag and drop. + gtk_drag_dest_set(window_contents_, static_cast<GtkDestDefaults>(0), + NULL, 0, GDK_ACTION_COPY); + signal_registrar_->Connect(window_contents_, "drag_motion", + G_CALLBACK(&OnDragMotionThunk), this); + signal_registrar_->Connect(window_contents_, "drag_data_received", + G_CALLBACK(&OnDragDataReceivedThunk), this); + signal_registrar_->Connect(window_contents_, "drag_drop", + G_CALLBACK(&OnDragDropThunk), this); + signal_registrar_->Connect(window_contents_, "drag_leave", + G_CALLBACK(&OnDragLeaveThunk), this); + signal_registrar_->Connect(window_contents_, "drag_data_get", + G_CALLBACK(&OnDragDataGetThunk), this); + signal_registrar_->Connect(window_contents_, "drag_end", + G_CALLBACK(&OnDragEndThunk), this); + signal_registrar_->Connect(window_contents_, "drag_failed", + G_CALLBACK(&OnDragFailedThunk), this); + signal_registrar_->Connect(G_OBJECT(widget_), "window-state-event", + G_CALLBACK(&OnWindowStateEventThunk), this); + + ui::GObjectDestructorFILO::GetInstance()->Connect( + G_OBJECT(widget_), &OnDestroyedThunk, this); + +#if defined(TOUCH_UI) + if (params.type != Widget::InitParams::TYPE_TOOLTIP) { + views::TooltipManagerViews* manager = new views::TooltipManagerViews( + static_cast<internal::RootView*>(GetWidget()->GetRootView())); + tooltip_manager_.reset(manager); + } +#else + tooltip_manager_.reset(new TooltipManagerGtk(this)); +#endif + + // Register for tooltips. + g_object_set(G_OBJECT(window_contents_), "has-tooltip", TRUE, NULL); + signal_registrar_->Connect(window_contents_, "query_tooltip", + G_CALLBACK(&OnQueryTooltipThunk), this); + + if (child_) { + if (modified_params.parent) + SetBounds(params.bounds); + } else { + gtk_widget_add_events(widget_, + GDK_STRUCTURE_MASK); + if (params.bounds.width() > 0 && params.bounds.height() > 0) + gtk_window_resize(GTK_WINDOW(widget_), params.bounds.width(), + params.bounds.height()); + gtk_window_move(GTK_WINDOW(widget_), params.bounds.x(), params.bounds.y()); + } +} + +NonClientFrameView* NativeWidgetGtk::CreateNonClientFrameView() { + return NULL; +} + +void NativeWidgetGtk::UpdateFrameAfterFrameChange() { + // We currently don't support different frame types on Gtk, so we don't + // need to implement this. + NOTIMPLEMENTED(); +} + +bool NativeWidgetGtk::ShouldUseNativeFrame() const { + return false; +} + +void NativeWidgetGtk::FrameTypeChanged() { + // This is called when the Theme has changed, so forward the event to the root + // widget. + GetWidget()->ThemeChanged(); + GetWidget()->GetRootView()->SchedulePaint(); +} + +Widget* NativeWidgetGtk::GetWidget() { + return delegate_->AsWidget(); +} + +const Widget* NativeWidgetGtk::GetWidget() const { + return delegate_->AsWidget(); +} + +gfx::NativeView NativeWidgetGtk::GetNativeView() const { + return widget_; +} + +gfx::NativeWindow NativeWidgetGtk::GetNativeWindow() const { + return child_ ? NULL : GTK_WINDOW(widget_); +} + +Widget* NativeWidgetGtk::GetTopLevelWidget() { + NativeWidgetPrivate* native_widget = GetTopLevelNativeWidget(GetNativeView()); + return native_widget ? native_widget->GetWidget() : NULL; +} + +const ui::Compositor* NativeWidgetGtk::GetCompositor() const { + return compositor_.get(); +} + +ui::Compositor* NativeWidgetGtk::GetCompositor() { + return compositor_.get(); +} + +void NativeWidgetGtk::CalculateOffsetToAncestorWithLayer( + gfx::Point* offset, + ui::Layer** layer_parent) { +} + +void NativeWidgetGtk::ReorderLayers() { +} + +void NativeWidgetGtk::ViewRemoved(View* view) { + if (drop_target_.get()) + drop_target_->ResetTargetViewIfEquals(view); +} + +void NativeWidgetGtk::SetNativeWindowProperty(const char* name, void* value) { + g_object_set_data(G_OBJECT(widget_), name, value); +} + +void* NativeWidgetGtk::GetNativeWindowProperty(const char* name) const { + return g_object_get_data(G_OBJECT(widget_), name); +} + +TooltipManager* NativeWidgetGtk::GetTooltipManager() const { + return tooltip_manager_.get(); +} + +bool NativeWidgetGtk::IsScreenReaderActive() const { + return false; +} + +void NativeWidgetGtk::SendNativeAccessibilityEvent( + View* view, + ui::AccessibilityTypes::Event event_type) { + // In the future if we add native GTK accessibility support, the + // notification should be sent here. +} + +void NativeWidgetGtk::SetMouseCapture() { + DCHECK(!HasMouseCapture()); + + // Release the current grab. + GtkWidget* current_grab_window = gtk_grab_get_current(); + if (current_grab_window) + gtk_grab_remove(current_grab_window); + + if (is_menu_ && gdk_pointer_is_grabbed()) + gdk_pointer_ungrab(GDK_CURRENT_TIME); + + // Make sure all app mouse/keyboard events are targeted at us only. + gtk_grab_add(window_contents_); + if (gtk_grab_get_current() == window_contents_ && !grab_notify_signal_id_) { + // "grab_notify" is sent any time the grab changes. We only care about grab + // changes when we have done a grab. + grab_notify_signal_id_ = g_signal_connect( + window_contents_, "grab_notify", G_CALLBACK(&OnGrabNotifyThunk), this); + } + + if (is_menu_) { + // For menus we do a pointer grab too. This ensures we get mouse events from + // other apps. In theory we should do this for all widget types, but doing + // so leads to gdk_pointer_grab randomly returning GDK_GRAB_ALREADY_GRABBED. + GdkGrabStatus pointer_grab_status = + gdk_pointer_grab(window_contents()->window, FALSE, + static_cast<GdkEventMask>( + GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | + GDK_POINTER_MOTION_MASK), + NULL, NULL, GDK_CURRENT_TIME); + // NOTE: technically grab may fail. We may want to try and continue on in + // that case. + DCHECK_EQ(GDK_GRAB_SUCCESS, pointer_grab_status); + has_pointer_grab_ = pointer_grab_status == GDK_GRAB_SUCCESS; + +#if defined(TOUCH_UI) + ::Window window = GDK_WINDOW_XID(window_contents()->window); + Display* display = GDK_WINDOW_XDISPLAY(window_contents()->window); + bool xi2grab = + ui::TouchFactory::GetInstance()->GrabTouchDevices(display, window); + // xi2grab should always succeed if has_pointer_grab_ succeeded. + DCHECK(xi2grab); + has_pointer_grab_ = has_pointer_grab_ && xi2grab; +#endif + } +} + +void NativeWidgetGtk::ReleaseMouseCapture() { + bool delegate_lost_capture = HasMouseCapture(); + if (GTK_WIDGET_HAS_GRAB(window_contents_)) + gtk_grab_remove(window_contents_); + if (grab_notify_signal_id_) { + g_signal_handler_disconnect(window_contents_, grab_notify_signal_id_); + grab_notify_signal_id_ = 0; + } + if (has_pointer_grab_) { + has_pointer_grab_ = false; + gdk_pointer_ungrab(GDK_CURRENT_TIME); +#if defined(TOUCH_UI) + ui::TouchFactory::GetInstance()->UngrabTouchDevices( + GDK_WINDOW_XDISPLAY(window_contents()->window)); +#endif + } + if (delegate_lost_capture) + delegate_->OnMouseCaptureLost(); +} + +bool NativeWidgetGtk::HasMouseCapture() const { + return GTK_WIDGET_HAS_GRAB(window_contents_) || has_pointer_grab_; +} + +InputMethod* NativeWidgetGtk::CreateInputMethod() { + // Create input method when pure views is enabled but not on views desktop. + // TODO(suzhe): Always enable input method when we start to use + // RenderWidgetHostViewViews in normal ChromeOS. + if (views::Widget::IsPureViews()) { +#if defined(HAVE_IBUS) + InputMethod* input_method = + InputMethodIBus::IsInputMethodIBusEnabled() ? + static_cast<InputMethod*>(new InputMethodIBus(this)) : + static_cast<InputMethod*>(new InputMethodGtk(this)); +#else + InputMethod* input_method = new InputMethodGtk(this); +#endif + input_method->Init(GetWidget()); + return input_method; + } + // GTK's textfield will handle IME. + return NULL; +} + +void NativeWidgetGtk::CenterWindow(const gfx::Size& size) { + gfx::Rect center_rect; + + GtkWindow* parent = gtk_window_get_transient_for(GetNativeWindow()); + if (parent) { + // We have a parent window, center over it. + gint parent_x = 0; + gint parent_y = 0; + gtk_window_get_position(parent, &parent_x, &parent_y); + gint parent_w = 0; + gint parent_h = 0; + gtk_window_get_size(parent, &parent_w, &parent_h); + center_rect = gfx::Rect(parent_x, parent_y, parent_w, parent_h); + } else { + // We have no parent window, center over the screen. + center_rect = gfx::Screen::GetMonitorWorkAreaNearestWindow(GetNativeView()); + } + gfx::Rect bounds(center_rect.x() + (center_rect.width() - size.width()) / 2, + center_rect.y() + (center_rect.height() - size.height()) / 2, + size.width(), size.height()); + GetWidget()->SetBoundsConstrained(bounds); +} + +void NativeWidgetGtk::GetWindowPlacement( + gfx::Rect* bounds, + ui::WindowShowState* show_state) const { + // Do nothing for now. ChromeOS isn't yet saving window placement. +} + +void NativeWidgetGtk::SetWindowTitle(const string16& title) { + // We don't have a window title on ChromeOS (right now). +} + +void NativeWidgetGtk::SetWindowIcons(const SkBitmap& window_icon, + const SkBitmap& app_icon) { + // We don't have window icons on ChromeOS. +} + +void NativeWidgetGtk::SetAccessibleName(const string16& name) { +} + +void NativeWidgetGtk::SetAccessibleRole(ui::AccessibilityTypes::Role role) { +} + +void NativeWidgetGtk::SetAccessibleState(ui::AccessibilityTypes::State state) { +} + +void NativeWidgetGtk::BecomeModal() { + gtk_window_set_modal(GetNativeWindow(), true); +} + +gfx::Rect NativeWidgetGtk::GetWindowScreenBounds() const { + // Client == Window bounds on Gtk. + return GetClientAreaScreenBounds(); +} + +gfx::Rect NativeWidgetGtk::GetClientAreaScreenBounds() const { + // Due to timing we can get a request for bounds after Close(). + // TODO(beng): Figure out if this is bogus. + if (!widget_) + return gfx::Rect(size_); + + int x = 0, y = 0, w = 0, h = 0; + if (GTK_IS_WINDOW(widget_)) { + gtk_window_get_position(GTK_WINDOW(widget_), &x, &y); + // NOTE: this doesn't include frame decorations, but it should be good + // enough for our uses. + gtk_window_get_size(GTK_WINDOW(widget_), &w, &h); + } else { + GetWidgetPositionOnScreen(widget_, &x, &y); + w = widget_->allocation.width; + h = widget_->allocation.height; + } + return gfx::Rect(x, y, w, h); +} + +gfx::Rect NativeWidgetGtk::GetRestoredBounds() const { + // We currently don't support tiling, so this doesn't matter. + return GetWindowScreenBounds(); +} + +void NativeWidgetGtk::SetBounds(const gfx::Rect& bounds) { + if (child_) { + GtkWidget* parent = gtk_widget_get_parent(widget_); + if (GTK_IS_VIEWS_FIXED(parent)) { + NativeWidgetGtk* parent_widget = static_cast<NativeWidgetGtk*>( + internal::NativeWidgetPrivate::GetNativeWidgetForNativeView(parent)); + parent_widget->PositionChild(widget_, bounds.x(), bounds.y(), + bounds.width(), bounds.height()); + } else { + DCHECK(GTK_IS_FIXED(parent)) + << "Parent of NativeWidgetGtk has to be Fixed or ViewsFixed"; + // Just request the size if the parent is not NativeWidgetGtk but plain + // GtkFixed. NativeWidgetGtk does not know the minimum size so we assume + // the caller of the SetBounds knows exactly how big it wants to be. + gtk_widget_set_size_request(widget_, bounds.width(), bounds.height()); + if (parent != null_parent_) + gtk_fixed_move(GTK_FIXED(parent), widget_, bounds.x(), bounds.y()); + } + } else { + if (GTK_WIDGET_MAPPED(widget_)) { + // If the widget is mapped (on screen), we can move and resize with one + // call, which avoids two separate window manager steps. + gdk_window_move_resize(widget_->window, bounds.x(), bounds.y(), + bounds.width(), bounds.height()); + } + + // Always call gtk_window_move and gtk_window_resize so that GtkWindow's + // geometry info is up-to-date. + GtkWindow* gtk_window = GTK_WINDOW(widget_); + // TODO: this may need to set an initial size if not showing. + // TODO: need to constrain based on screen size. + if (!bounds.IsEmpty()) { + gtk_window_resize(gtk_window, bounds.width(), bounds.height()); + } + gtk_window_move(gtk_window, bounds.x(), bounds.y()); + } +} + +void NativeWidgetGtk::SetSize(const gfx::Size& size) { + if (child_) { + GtkWidget* parent = gtk_widget_get_parent(widget_); + if (GTK_IS_VIEWS_FIXED(parent)) { + gtk_views_fixed_set_widget_size(widget_, size.width(), size.height()); + } else { + gtk_widget_set_size_request(widget_, size.width(), size.height()); + } + } else { + if (GTK_WIDGET_MAPPED(widget_)) + gdk_window_resize(widget_->window, size.width(), size.height()); + GtkWindow* gtk_window = GTK_WINDOW(widget_); + if (!size.IsEmpty()) + gtk_window_resize(gtk_window, size.width(), size.height()); + } +} + +void NativeWidgetGtk::MoveAbove(gfx::NativeView native_view) { + ui::StackPopupWindow(GetNativeView(), native_view); +} + +void NativeWidgetGtk::MoveToTop() { + DCHECK(GTK_IS_WINDOW(GetNativeView())); + gtk_window_present(GTK_WINDOW(GetNativeView())); +} + +void NativeWidgetGtk::SetShape(gfx::NativeRegion region) { + if (widget_ && widget_->window) { + gdk_window_shape_combine_region(widget_->window, region, 0, 0); + gdk_region_destroy(region); + } +} + +void NativeWidgetGtk::Close() { + if (!widget_) + return; // No need to do anything. + + // Hide first. + Hide(); + if (!close_widget_factory_.HasWeakPtrs()) { + // And we delay the close just in case we're on the stack. + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&NativeWidgetGtk::CloseNow, + close_widget_factory_.GetWeakPtr())); + } +} + +void NativeWidgetGtk::CloseNow() { + if (widget_) { + gtk_widget_destroy(widget_); // Triggers OnDestroy(). + } +} + +void NativeWidgetGtk::EnableClose(bool enable) { + gtk_window_set_deletable(GetNativeWindow(), enable); +} + +void NativeWidgetGtk::Show() { + if (widget_) { + gtk_widget_show(widget_); + if (widget_->window) + gdk_window_raise(widget_->window); + } +} + +void NativeWidgetGtk::Hide() { + if (widget_) { + gtk_widget_hide(widget_); + if (widget_->window) + gdk_window_lower(widget_->window); + } +} + +void NativeWidgetGtk::ShowMaximizedWithBounds( + const gfx::Rect& restored_bounds) { + // TODO: when we add maximization support update this. + Show(); +} + +void NativeWidgetGtk::ShowWithWindowState(ui::WindowShowState show_state) { + // No concept of maximization (yet) on ChromeOS. + if (show_state == ui::SHOW_STATE_INACTIVE) + gtk_window_set_focus_on_map(GetNativeWindow(), false); + gtk_widget_show(GetNativeView()); +} + +bool NativeWidgetGtk::IsVisible() const { + return GTK_WIDGET_VISIBLE(GetNativeView()) && (GetWidget()->is_top_level() || + GetWidget()->GetTopLevelWidget()->IsVisible()); +} + +void NativeWidgetGtk::Activate() { + gtk_window_present(GetNativeWindow()); +} + +void NativeWidgetGtk::Deactivate() { + gdk_window_lower(GTK_WIDGET(GetNativeView())->window); +} + +bool NativeWidgetGtk::IsActive() const { + DCHECK(!child_); + return is_active_; +} + +void NativeWidgetGtk::SetAlwaysOnTop(bool on_top) { + DCHECK(!child_); + always_on_top_ = on_top; + if (widget_) + gtk_window_set_keep_above(GTK_WINDOW(widget_), on_top); +} + +void NativeWidgetGtk::Maximize() { +#if defined(TOUCH_UI) + // There may not be a window manager. So maximize ourselves: move to the + // top-left corner and resize to the entire bounds of the screen. + gfx::Rect screen = gfx::Screen::GetMonitorAreaNearestWindow(GetNativeView()); + gtk_window_move(GTK_WINDOW(GetNativeWindow()), screen.x(), screen.y()); + // TODO(backer): Remove this driver bug workaround once it is fixed. + gtk_window_resize(GTK_WINDOW(GetNativeWindow()), + screen.width() - 1, screen.height()); +#else + gtk_window_maximize(GetNativeWindow()); +#endif +} + +void NativeWidgetGtk::Minimize() { + gtk_window_iconify(GetNativeWindow()); +} + +bool NativeWidgetGtk::IsMaximized() const { + return window_state_ & GDK_WINDOW_STATE_MAXIMIZED; +} + +bool NativeWidgetGtk::IsMinimized() const { + return window_state_ & GDK_WINDOW_STATE_ICONIFIED; +} + +void NativeWidgetGtk::Restore() { + if (IsFullscreen()) { + SetFullscreen(false); + } else { + if (IsMaximized()) + gtk_window_unmaximize(GetNativeWindow()); + else if (IsMinimized()) + gtk_window_deiconify(GetNativeWindow()); + } +} + +void NativeWidgetGtk::SetFullscreen(bool fullscreen) { + if (fullscreen) + gtk_window_fullscreen(GetNativeWindow()); + else + gtk_window_unfullscreen(GetNativeWindow()); +} + +bool NativeWidgetGtk::IsFullscreen() const { + return window_state_ & GDK_WINDOW_STATE_FULLSCREEN; +} + +void NativeWidgetGtk::SetOpacity(unsigned char opacity) { + opacity_ = opacity; + if (widget_) { + // We can only set the opacity when the widget has been realized. + gdk_window_set_opacity(widget_->window, static_cast<gdouble>(opacity) / + static_cast<gdouble>(255)); + } +} + +void NativeWidgetGtk::SetUseDragFrame(bool use_drag_frame) { + NOTIMPLEMENTED(); +} + +bool NativeWidgetGtk::IsAccessibleWidget() const { + return false; +} + +void NativeWidgetGtk::RunShellDrag(View* view, + const ui::OSExchangeData& data, + int operation) { + DoDrag(data, operation); +} + +void NativeWidgetGtk::SchedulePaintInRect(const gfx::Rect& rect) { + // No need to schedule paint if + // 1) widget_ is NULL. This may happen because this instance may + // be deleted after the gtk widget has been destroyed (See OnDestroy()). + // 2) widget_ is not drawable (mapped and visible) + // 3) If it's never painted before. The first expose event will + // paint the area that has to be painted. + if (widget_ && GTK_WIDGET_DRAWABLE(widget_) && painted_) { + gtk_widget_queue_draw_area(widget_, rect.x(), rect.y(), rect.width(), + rect.height()); + } +} + +void NativeWidgetGtk::SetCursor(gfx::NativeCursor cursor) { +#if defined(TOUCH_UI) + if (!ui::TouchFactory::GetInstance()->is_cursor_visible()) + cursor = gfx::GetCursor(GDK_BLANK_CURSOR); +#endif + // |window_contents_| is placed on top of |widget_|. So the cursor needs to be + // set on |window_contents_| instead of |widget_|. + if (window_contents_) + gdk_window_set_cursor(window_contents_->window, cursor); +} + +void NativeWidgetGtk::ClearNativeFocus() { + DCHECK(!child_); + if (!GetNativeView()) { + NOTREACHED(); + return; + } + gtk_window_set_focus(GTK_WINDOW(GetNativeView()), NULL); +} + +void NativeWidgetGtk::FocusNativeView(gfx::NativeView native_view) { + if (native_view && !gtk_widget_is_focus(native_view)) + gtk_widget_grab_focus(native_view); +} + +bool NativeWidgetGtk::ConvertPointFromAncestor( + const Widget* ancestor, gfx::Point* point) const { + NOTREACHED(); + return false; +} + +gfx::Rect NativeWidgetGtk::GetWorkAreaBoundsInScreen() const { + return gfx::Screen::GetMonitorWorkAreaNearestWindow(GetNativeView()); +} + +void NativeWidgetGtk::SetInactiveRenderingDisabled(bool value) { +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetGtk, protected: + +void NativeWidgetGtk::OnSizeRequest(GtkWidget* widget, + GtkRequisition* requisition) { + // Do only return the preferred size for child windows. GtkWindow interprets + // the requisition as a minimum size for top level windows, returning a + // preferred size for these would prevents us from setting smaller window + // sizes. + if (child_) { + gfx::Size size(GetWidget()->GetRootView()->GetPreferredSize()); + requisition->width = size.width(); + requisition->height = size.height(); + } +} + +void NativeWidgetGtk::OnSizeAllocate(GtkWidget* widget, + GtkAllocation* allocation) { + // See comment next to size_ as to why we do this. Also note, it's tempting + // to put this in the static method so subclasses don't need to worry about + // it, but if a subclasses needs to set a shape then they need to always + // reset the shape in this method regardless of whether the size changed. + gfx::Size new_size(allocation->width, allocation->height); + if (new_size == size_) + return; + size_ = new_size; + if (compositor_.get()) + compositor_->WidgetSizeChanged(size_); + delegate_->OnNativeWidgetSizeChanged(size_); + + if (GetWidget()->non_client_view()) { + // The Window's NonClientView may provide a custom shape for the Window. + gfx::Path window_mask; + GetWidget()->non_client_view()->GetWindowMask(gfx::Size(allocation->width, + allocation->height), + &window_mask); + GdkRegion* mask_region = window_mask.CreateNativeRegion(); + gdk_window_shape_combine_region(GetNativeView()->window, mask_region, 0, 0); + if (mask_region) + gdk_region_destroy(mask_region); + + SaveWindowPosition(); + } +} + +gboolean NativeWidgetGtk::OnPaint(GtkWidget* widget, GdkEventExpose* event) { + gdk_window_set_debug_updates(Widget::IsDebugPaintEnabled()); + + if (transparent_ && child_) { + // Clear the background before drawing any view and native components. + DrawTransparentBackground(widget, event); + if (!CompositePainter::IsComposited(widget_) && + gdk_screen_is_composited(gdk_screen_get_default())) { + // Let the parent draw the content only after something is drawn on + // the widget. + CompositePainter::SetComposited(widget_); + } + } + + ui::ScopedRegion region(gdk_region_copy(event->region)); + if (!gdk_region_empty(region.Get())) { + GdkRectangle clip_bounds; + gdk_region_get_clipbox(region.Get(), &clip_bounds); + if (!delegate_->OnNativeWidgetPaintAccelerated(gfx::Rect(clip_bounds))) { + gfx::CanvasSkiaPaint canvas(event); + if (!canvas.is_empty()) { + canvas.set_composite_alpha(is_transparent()); + delegate_->OnNativeWidgetPaint(&canvas); + } + } + } + + if (!painted_) { + painted_ = true; + if (!child_) + UpdateFreezeUpdatesProperty(GTK_WINDOW(widget_), false /* remove */); + } + return false; // False indicates other widgets should get the event as well. +} + +void NativeWidgetGtk::OnDragDataGet(GtkWidget* widget, + GdkDragContext* context, + GtkSelectionData* data, + guint info, + guint time) { + if (!drag_data_) { + NOTREACHED(); + return; + } + drag_data_->WriteFormatToSelection(info, data); +} + +void NativeWidgetGtk::OnDragDataReceived(GtkWidget* widget, + GdkDragContext* context, + gint x, + gint y, + GtkSelectionData* data, + guint info, + guint time) { + if (drop_target_.get()) + drop_target_->OnDragDataReceived(context, x, y, data, info, time); +} + +gboolean NativeWidgetGtk::OnDragDrop(GtkWidget* widget, + GdkDragContext* context, + gint x, + gint y, + guint time) { + if (drop_target_.get()) { + return drop_target_->OnDragDrop(context, x, y, time); + } + return FALSE; +} + +void NativeWidgetGtk::OnDragEnd(GtkWidget* widget, GdkDragContext* context) { + if (!drag_data_) { + // This indicates we didn't start a drag operation, and should never + // happen. + NOTREACHED(); + return; + } + // Quit the nested message loop we spawned in DoDrag. + MessageLoop::current()->Quit(); +} + +gboolean NativeWidgetGtk::OnDragFailed(GtkWidget* widget, + GdkDragContext* context, + GtkDragResult result) { + return FALSE; +} + +void NativeWidgetGtk::OnDragLeave(GtkWidget* widget, + GdkDragContext* context, + guint time) { + if (ignore_drag_leave_) { + ignore_drag_leave_ = false; + return; + } + if (drop_target_.get()) { + drop_target_->OnDragLeave(context, time); + drop_target_.reset(NULL); + } +} + +gboolean NativeWidgetGtk::OnDragMotion(GtkWidget* widget, + GdkDragContext* context, + gint x, + gint y, + guint time) { + if (!drop_target_.get()) { + drop_target_.reset(new DropTargetGtk( + reinterpret_cast<internal::RootView*>(GetWidget()->GetRootView()), + context)); + } + return drop_target_->OnDragMotion(context, x, y, time); +} + +gboolean NativeWidgetGtk::OnEnterNotify(GtkWidget* widget, + GdkEventCrossing* event) { + if (HasMouseCapture() && event->mode == GDK_CROSSING_GRAB) { + // Doing a grab results an async enter event, regardless of where the mouse + // is. We don't want to generate a mouse move in this case. + return false; + } + + if (!GetWidget()->last_mouse_event_was_move_ && + !GetWidget()->is_mouse_button_pressed_) { + // When a mouse button is pressed gtk generates a leave, enter, press. + // RootView expects to get a mouse move before a press, otherwise enter is + // not set. So we generate a move here. + GdkEventMotion motion = { GDK_MOTION_NOTIFY, event->window, + event->send_event, event->time, event->x, event->y, NULL, event->state, + 0, NULL, event->x_root, event->y_root }; + + // If this event is the result of pressing a button then one of the button + // modifiers is set. Unset it as we're compensating for the leave generated + // when you press a button. + motion.state &= ~(GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK); + + MouseEvent mouse_event(TransformEvent(&motion)); + delegate_->OnMouseEvent(mouse_event); + } + return false; +} + +gboolean NativeWidgetGtk::OnLeaveNotify(GtkWidget* widget, + GdkEventCrossing* event) { + gdk_window_set_cursor(widget->window, gfx::GetCursor(GDK_LEFT_PTR)); + + GetWidget()->ResetLastMouseMoveFlag(); + + if (!HasMouseCapture() && !GetWidget()->is_mouse_button_pressed_) { + // Don't convert if the event is synthetic and has 0x0 coordinates. + if (event->x_root || event->y_root || event->x || event->y || + !event->send_event) { + TransformEvent(event); + } + MouseEvent mouse_event(reinterpret_cast<GdkEvent*>(event)); + delegate_->OnMouseEvent(mouse_event); + } + return false; +} + +gboolean NativeWidgetGtk::OnMotionNotify(GtkWidget* widget, + GdkEventMotion* event) { + if (GetWidget()->non_client_view()) { + GdkEventMotion transformed_event = *event; + TransformEvent(&transformed_event); + gfx::Point translated_location(transformed_event.x, transformed_event.y); + + // Update the cursor for the screen edge. + int hittest_code = + GetWidget()->non_client_view()->NonClientHitTest(translated_location); + if (hittest_code != HTCLIENT) { + GdkCursorType cursor_type = HitTestCodeToGdkCursorType(hittest_code); + gdk_window_set_cursor(widget->window, gfx::GetCursor(cursor_type)); + } + } + + MouseEvent mouse_event(TransformEvent(event)); + delegate_->OnMouseEvent(mouse_event); + return true; +} + +gboolean NativeWidgetGtk::OnButtonPress(GtkWidget* widget, + GdkEventButton* event) { + if (GetWidget()->non_client_view()) { + GdkEventButton transformed_event = *event; + MouseEvent mouse_event(TransformEvent(&transformed_event)); + + int hittest_code = GetWidget()->non_client_view()->NonClientHitTest( + mouse_event.location()); + switch (hittest_code) { + case HTCAPTION: { + // Start dragging if the mouse event is a single click and *not* a right + // click. If it is a right click, then pass it through to + // NativeWidgetGtk::OnButtonPress so that View class can show + // ContextMenu upon a mouse release event. We only start drag on single + // clicks as we get a crash in Gtk on double/triple clicks. + if (event->type == GDK_BUTTON_PRESS && + !mouse_event.IsOnlyRightMouseButton()) { + gfx::Point screen_point(event->x, event->y); + View::ConvertPointToScreen(GetWidget()->GetRootView(), &screen_point); + gtk_window_begin_move_drag(GetNativeWindow(), event->button, + screen_point.x(), screen_point.y(), + event->time); + return TRUE; + } + break; + } + case HTBOTTOM: + case HTBOTTOMLEFT: + case HTBOTTOMRIGHT: + case HTGROWBOX: + case HTLEFT: + case HTRIGHT: + case HTTOP: + case HTTOPLEFT: + case HTTOPRIGHT: { + gfx::Point screen_point(event->x, event->y); + View::ConvertPointToScreen(GetWidget()->GetRootView(), &screen_point); + // TODO(beng): figure out how to get a good minimum size. + gtk_widget_set_size_request(GetNativeView(), 100, 100); + gtk_window_begin_resize_drag(GetNativeWindow(), + HitTestCodeToGDKWindowEdge(hittest_code), + event->button, screen_point.x(), + screen_point.y(), event->time); + return TRUE; + } + default: + // Everything else falls into standard client event handling... + break; + } + } + + if (event->type == GDK_2BUTTON_PRESS || event->type == GDK_3BUTTON_PRESS) { + // The sequence for double clicks is press, release, press, 2press, release. + // This means that at the time we get the second 'press' we don't know + // whether it corresponds to a double click or not. For now we're completely + // ignoring the 2press/3press events as they are duplicate. To make this + // work right we need to write our own code that detects if the press is a + // double/triple. For now we're completely punting, which means we always + // get single clicks. + // TODO: fix this. + return true; + } + + MouseEvent mouse_event(TransformEvent(event)); + // Returns true to consume the event when widget is not transparent. + return delegate_->OnMouseEvent(mouse_event) || !transparent_; +} + +gboolean NativeWidgetGtk::OnButtonRelease(GtkWidget* widget, + GdkEventButton* event) { + // GTK generates a mouse release at the end of dnd. We need to ignore it. + if (!drag_data_) { + MouseEvent mouse_event(TransformEvent(event)); + delegate_->OnMouseEvent(mouse_event); + } + return true; +} + +gboolean NativeWidgetGtk::OnScroll(GtkWidget* widget, GdkEventScroll* event) { + MouseWheelEvent mouse_event(TransformEvent(event)); + return delegate_->OnMouseEvent(mouse_event); +} + +gboolean NativeWidgetGtk::OnFocusIn(GtkWidget* widget, GdkEventFocus* event) { + if (has_focus_) + return false; // This is the second focus-in event in a row, ignore it. + has_focus_ = true; + + should_handle_menu_key_release_ = false; + + if (!GetWidget()->is_top_level()) + return false; + + // Only top-level Widget should have an InputMethod instance. + InputMethod* input_method = GetWidget()->GetInputMethod(); + if (input_method) + input_method->OnFocus(); + + // See description of got_initial_focus_in_ for details on this. + if (!got_initial_focus_in_) { + got_initial_focus_in_ = true; + // Sets initial focus here. On X11/Gtk, window creation + // is asynchronous and a focus request has to be made after a window + // gets created. + GetWidget()->SetInitialFocus(); + } + return false; +} + +gboolean NativeWidgetGtk::OnFocusOut(GtkWidget* widget, GdkEventFocus* event) { + if (!has_focus_) + return false; // This is the second focus-out event in a row, ignore it. + has_focus_ = false; + + if (!GetWidget()->is_top_level()) + return false; + + // Only top-level Widget should have an InputMethod instance. + InputMethod* input_method = GetWidget()->GetInputMethod(); + if (input_method) + input_method->OnBlur(); + return false; +} + +gboolean NativeWidgetGtk::OnEventKey(GtkWidget* widget, GdkEventKey* event) { + KeyEvent key(reinterpret_cast<GdkEvent*>(event)); + InputMethod* input_method = GetWidget()->GetInputMethod(); + if (input_method) + input_method->DispatchKeyEvent(key); + else + DispatchKeyEventPostIME(key); + + // Returns true to prevent GtkWindow's default key event handler. + return true; +} + +gboolean NativeWidgetGtk::OnQueryTooltip(GtkWidget* widget, + gint x, + gint y, + gboolean keyboard_mode, + GtkTooltip* tooltip) { +#if defined(TOUCH_UI) + return false; // Tell GTK not to draw tooltips as we draw tooltips in views +#else + return static_cast<TooltipManagerGtk*>(tooltip_manager_.get())-> + ShowTooltip(x, y, keyboard_mode, tooltip); +#endif +} + +gboolean NativeWidgetGtk::OnVisibilityNotify(GtkWidget* widget, + GdkEventVisibility* event) { + return false; +} + +gboolean NativeWidgetGtk::OnGrabBrokeEvent(GtkWidget* widget, GdkEvent* event) { + if (!has_pointer_grab_ && !has_keyboard_grab_) { + // We don't have any grabs; don't attempt to do anything. + return false; + } + + // Sent when either the keyboard or pointer grab is broke. We drop both grabs + // in this case. + if (event->grab_broken.keyboard) { + // Keyboard grab was broke. + has_keyboard_grab_ = false; + if (has_pointer_grab_) { + has_pointer_grab_ = false; + gdk_pointer_ungrab(GDK_CURRENT_TIME); + delegate_->OnMouseCaptureLost(); + } + } else { + // Mouse grab was broke. + has_pointer_grab_ = false; + if (has_keyboard_grab_) { + has_keyboard_grab_ = false; + gdk_keyboard_ungrab(GDK_CURRENT_TIME); + } + delegate_->OnMouseCaptureLost(); + } + ReleaseMouseCapture(); + +#if defined(TOUCH_UI) + ui::TouchFactory::GetInstance()->UngrabTouchDevices( + GDK_WINDOW_XDISPLAY(window_contents()->window)); +#endif + return false; // To let other widgets get the event. +} + +void NativeWidgetGtk::OnGrabNotify(GtkWidget* widget, gboolean was_grabbed) { + // Sent when gtk_grab_add changes. + if (!window_contents_) + return; // Grab broke after window destroyed, don't try processing it. + if (!was_grabbed) // Indicates we've been shadowed (lost grab). + HandleGtkGrabBroke(); +} + +void NativeWidgetGtk::OnDestroy(GtkWidget* object) { + signal_registrar_.reset(); + if (grab_notify_signal_id_) { + g_signal_handler_disconnect(window_contents_, grab_notify_signal_id_); + grab_notify_signal_id_ = 0; + } + delegate_->OnNativeWidgetDestroying(); + if (!child_) + ActiveWindowWatcherX::RemoveObserver(this); + // Note that this handler is hooked to GtkObject::destroy. + // NULL out pointers here since we might still be in an observer list + // until deletion happens. + widget_ = window_contents_ = NULL; +} + +void NativeWidgetGtk::OnDestroyed(GObject *where_the_object_was) { + delegate_->OnNativeWidgetDestroyed(); + if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) + delete this; +} + +void NativeWidgetGtk::OnShow(GtkWidget* widget) { + delegate_->OnNativeWidgetVisibilityChanged(true); +} + +void NativeWidgetGtk::OnMap(GtkWidget* widget) { +#if defined(TOUCH_UI) + // Force an expose event to trigger OnPaint for touch. This is + // a workaround for a bug that X Expose event does not trigger + // Gdk's expose signal. This happens when you try to open views menu + // while a virtual keyboard gets kicked in or out. This seems to be + // a bug in message_pump_x.cc as we do get X Expose event but + // it doesn't trigger gtk's expose signal. We're not going to fix this + // as we're removing gtk and migrating to new compositor. + gdk_window_process_all_updates(); +#endif +} + +void NativeWidgetGtk::OnHide(GtkWidget* widget) { + delegate_->OnNativeWidgetVisibilityChanged(false); +} + +gboolean NativeWidgetGtk::OnWindowStateEvent(GtkWidget* widget, + GdkEventWindowState* event) { + if (GetWidget()->non_client_view() && + !(event->new_window_state & GDK_WINDOW_STATE_WITHDRAWN)) { + SaveWindowPosition(); + } + window_state_ = event->new_window_state; + return FALSE; +} + +gboolean NativeWidgetGtk::OnConfigureEvent(GtkWidget* widget, + GdkEventConfigure* event) { + SaveWindowPosition(); + return FALSE; +} + +void NativeWidgetGtk::HandleGtkGrabBroke() { + ReleaseMouseCapture(); + delegate_->OnMouseCaptureLost(); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetGtk, private: + +void NativeWidgetGtk::ScheduleDraw() { + SchedulePaintInRect(gfx::Rect(gfx::Point(), size_)); +} + +void NativeWidgetGtk::DispatchKeyEventPostIME(const KeyEvent& key) { + // Always reset |should_handle_menu_key_release_| unless we are handling a + // VKEY_MENU key release event. It ensures that VKEY_MENU accelerator can only + // be activated when handling a VKEY_MENU key release event which is preceded + // by an unhandled VKEY_MENU key press event. See also HandleKeyboardEvent(). + if (key.key_code() != ui::VKEY_MENU || key.type() != ui::ET_KEY_RELEASED) + should_handle_menu_key_release_ = false; + + // Send the key event to View hierarchy first. + bool handled = delegate_->OnKeyEvent(key); + + if (key.key_code() == ui::VKEY_PROCESSKEY || handled) + return; + + // Dispatch the key event to native GtkWidget hierarchy. + // To prevent GtkWindow from handling the key event as a keybinding, we need + // to bypass GtkWindow's default key event handler and dispatch the event + // here. + GdkEventKey* event = reinterpret_cast<GdkEventKey*>(key.gdk_event()); + if (!handled && event && GTK_IS_WINDOW(widget_)) + handled = gtk_window_propagate_key_event(GTK_WINDOW(widget_), event); + + // On Linux, in order to handle VKEY_MENU (Alt) accelerator key correctly and + // avoid issues like: http://crbug.com/40966 and http://crbug.com/49701, we + // should only send the key event to the focus manager if it's not handled by + // any View or native GtkWidget. + // The flow is different when the focus is in a RenderWidgetHostViewGtk, which + // always consumes the key event and send it back to us later by calling + // HandleKeyboardEvent() directly, if it's not handled by webkit. + if (!handled) + handled = HandleKeyboardEvent(key); + + // Dispatch the key event for bindings processing. + if (!handled && event && GTK_IS_WINDOW(widget_)) + gtk_bindings_activate_event(GTK_OBJECT(widget_), event); +} + +void NativeWidgetGtk::SetInitParams(const Widget::InitParams& params) { + DCHECK(!GetNativeView()); + + ownership_ = params.ownership; + child_ = params.child; + is_menu_ = params.type == Widget::InitParams::TYPE_MENU; + + // TODO(beng): The secondary checks here actually obviate the need for + // params.transient but that's only because NativeWidgetGtk + // considers any top-level widget to be a transient widget. We + // will probably want to ammend this assumption at some point. + if (params.transient || params.parent || params.parent_widget) + transient_to_parent_ = true; + if (params.transparent) + MakeTransparent(); + if (!params.accept_events && !child_) + ignore_events_ = true; + if (params.double_buffer) + EnableDoubleBuffer(true); +} + +gboolean NativeWidgetGtk::OnWindowPaint(GtkWidget* widget, + GdkEventExpose* event) { + // Clear the background to be totally transparent. We don't need to + // paint the root view here as that is done by OnPaint. + DCHECK(transparent_); + DrawTransparentBackground(widget, event); + // The Keyboard layout view has a renderer that covers the entire + // window, which prevents OnPaint from being called on window_contents_, + // so we need to remove the FREEZE_UPDATES property here. + if (!painted_) { + painted_ = true; + UpdateFreezeUpdatesProperty(GTK_WINDOW(widget_), false /* remove */); + } + return false; +} + +void NativeWidgetGtk::OnChildExpose(GtkWidget* child) { + DCHECK(!child_); + if (!painted_) { + painted_ = true; + UpdateFreezeUpdatesProperty(GTK_WINDOW(widget_), false /* remove */); + } + RemoveExposeHandlerIfExists(child); +} + +// static +gboolean NativeWidgetGtk::ChildExposeHandler(GtkWidget* widget, + GdkEventExpose* event) { + GtkWidget* toplevel = gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW); + CHECK(toplevel); + Widget* views_widget = Widget::GetWidgetForNativeView(toplevel); + CHECK(views_widget); + NativeWidgetGtk* widget_gtk = + static_cast<NativeWidgetGtk*>(views_widget->native_widget()); + widget_gtk->OnChildExpose(widget); + return false; +} + +void NativeWidgetGtk::CreateGtkWidget(const Widget::InitParams& params) { + // We turn off double buffering for two reasons: + // 1. We draw to a canvas then composite to the screen, which means we're + // doing our own double buffering already. + // 2. GTKs double buffering clips to the dirty region. RootView occasionally + // needs to expand the paint region (see RootView::OnPaint). This means + // that if we use GTK's double buffering and we tried to expand the dirty + // region, it wouldn't get painted. + if (child_) { + window_contents_ = widget_ = gtk_views_fixed_new(); + gtk_widget_set_name(widget_, "views-gtkwidget-child-fixed"); + if (!is_double_buffered_) + GTK_WIDGET_UNSET_FLAGS(widget_, GTK_DOUBLE_BUFFERED); + gtk_fixed_set_has_window(GTK_FIXED(widget_), true); + if (!params.parent && !null_parent_) { + GtkWidget* popup = gtk_window_new(GTK_WINDOW_POPUP); + null_parent_ = gtk_fixed_new(); + gtk_widget_set_name(null_parent_, "views-gtkwidget-null-parent"); + gtk_container_add(GTK_CONTAINER(popup), null_parent_); + gtk_widget_realize(null_parent_); + } + if (transparent_) { + // transparency has to be configured before widget is realized. + DCHECK(params.parent) << + "Transparent widget must have parent when initialized"; + ConfigureWidgetForTransparentBackground(params.parent); + } + gtk_container_add( + GTK_CONTAINER(params.parent ? params.parent : null_parent_), widget_); + gtk_widget_realize(widget_); + if (transparent_) { + // The widget has to be realized to set composited flag. + // I tried "realize" signal to set this flag, but it did not work + // when the top level is popup. + DCHECK(GTK_WIDGET_REALIZED(widget_)); + gdk_window_set_composited(widget_->window, true); + } + if (params.parent && !params.bounds.size().IsEmpty()) { + // Make sure that an widget is given it's initial size before + // we're done initializing, to take care of some potential + // corner cases when programmatically arranging hierarchies as + // seen in + // http://code.google.com/p/chromium-os/issues/detail?id=5987 + + // This can't be done without a parent present, or stale data + // might show up on the screen as seen in + // http://code.google.com/p/chromium/issues/detail?id=53870 + GtkAllocation alloc = + { 0, 0, params.bounds.width(), params.bounds.height() }; + gtk_widget_size_allocate(widget_, &alloc); + } + if (params.type == Widget::InitParams::TYPE_CONTROL) { + // Controls are initially visible. + gtk_widget_show(widget_); + } + } else { + Widget::InitParams::Type type = params.type; + if (type == Widget::InitParams::TYPE_BUBBLE && + params.delegate->AsBubbleDelegate() && + params.delegate->AsBubbleDelegate()->use_focusless()) { + // Handles focusless bubble type, which are bubbles that should + // act like popups rather than gtk windows. They do not get focus + // and are not controlled by window manager placement. + type = Widget::InitParams::TYPE_POPUP; + } + + // Use our own window class to override GtkWindow's move_focus method. + widget_ = gtk_views_window_new(WindowTypeToGtkWindowType(type)); + gtk_widget_set_name(widget_, "views-gtkwidget-window"); + if (transient_to_parent_) { + gtk_window_set_transient_for(GTK_WINDOW(widget_), + GTK_WINDOW(params.parent)); + } + GTK_WIDGET_UNSET_FLAGS(widget_, GTK_DOUBLE_BUFFERED); + + // Gtk determines the size for windows based on the requested size of the + // child. For NativeWidgetGtk the child is a fixed. If the fixed ends up + // with a child widget it's possible the child widget will drive the + // requested size of the widget, which we don't want. We explicitly set a + // value of 1x1 here so that gtk doesn't attempt to resize the window if we + // end up with a situation where the requested size of a child of the fixed + // is greater than the size of the window. By setting the size in this + // manner we're also allowing users of WidgetGtk to change the requested + // size at any time. + gtk_widget_set_size_request(widget_, 1, 1); + + if (!params.bounds.size().IsEmpty()) { + // When we realize the window, the window manager is given a size. If we + // don't specify a size before then GTK defaults to 200x200. Specify + // a size now so that the window manager sees the requested size. + GtkAllocation alloc = + { 0, 0, params.bounds.width(), params.bounds.height() }; + gtk_widget_size_allocate(widget_, &alloc); + } + gtk_window_set_decorated(GTK_WINDOW(widget_), false); + // We'll take care of positioning our window. + gtk_window_set_position(GTK_WINDOW(widget_), GTK_WIN_POS_NONE); + + window_contents_ = gtk_views_fixed_new(); + gtk_widget_set_name(window_contents_, "views-gtkwidget-window-fixed"); + if (!is_double_buffered_) + GTK_WIDGET_UNSET_FLAGS(window_contents_, GTK_DOUBLE_BUFFERED); + gtk_fixed_set_has_window(GTK_FIXED(window_contents_), true); + gtk_container_add(GTK_CONTAINER(widget_), window_contents_); + gtk_widget_show(window_contents_); + g_object_set_data(G_OBJECT(window_contents_), kNativeWidgetKey, + static_cast<NativeWidgetGtk*>(this)); + if (transparent_) + ConfigureWidgetForTransparentBackground(NULL); + + if (ignore_events_) + ConfigureWidgetForIgnoreEvents(); + + // Realize the window_contents_ so that we can always get a handle for + // acceleration. Without this we need to check every time paint is + // invoked. + gtk_widget_realize(window_contents_); + + SetAlwaysOnTop(always_on_top_); + // UpdateFreezeUpdatesProperty will realize the widget and handlers like + // size-allocate will function properly. + UpdateFreezeUpdatesProperty(GTK_WINDOW(widget_), true /* add */); + } + SetNativeWindowProperty(kNativeWidgetKey, this); +} + +void NativeWidgetGtk::ConfigureWidgetForTransparentBackground( + GtkWidget* parent) { + DCHECK(widget_ && window_contents_); + + GdkColormap* rgba_colormap = + gdk_screen_get_rgba_colormap(gtk_widget_get_screen(widget_)); + if (!rgba_colormap) { + transparent_ = false; + return; + } + // To make the background transparent we need to install the RGBA colormap + // on both the window and fixed. In addition we need to make sure no + // decorations are drawn. The last bit is to make sure the widget doesn't + // attempt to draw a pixmap in it's background. + if (!child_) { + DCHECK(parent == NULL); + gtk_widget_set_colormap(widget_, rgba_colormap); + gtk_widget_set_app_paintable(widget_, true); + signal_registrar_->Connect(widget_, "expose_event", + G_CALLBACK(&OnWindowPaintThunk), this); + gtk_widget_realize(widget_); + gdk_window_set_decorations(widget_->window, + static_cast<GdkWMDecoration>(0)); + } else { + DCHECK(parent); + CompositePainter::AddCompositePainter(parent); + } + DCHECK(!GTK_WIDGET_REALIZED(window_contents_)); + gtk_widget_set_colormap(window_contents_, rgba_colormap); +} + +void NativeWidgetGtk::ConfigureWidgetForIgnoreEvents() { + gtk_widget_realize(widget_); + GdkWindow* gdk_window = widget_->window; + Display* display = GDK_WINDOW_XDISPLAY(gdk_window); + XID win = GDK_WINDOW_XID(gdk_window); + + // This sets the clickable area to be empty, allowing all events to be + // passed to any windows behind this one. + XShapeCombineRectangles( + display, + win, + ShapeInput, + 0, // x offset + 0, // y offset + NULL, // rectangles + 0, // num rectangles + ShapeSet, + 0); +} + +void NativeWidgetGtk::DrawTransparentBackground(GtkWidget* widget, + GdkEventExpose* event) { + cairo_t* cr = gdk_cairo_create(widget->window); + cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); + gdk_cairo_region(cr, event->region); + cairo_fill(cr); + cairo_destroy(cr); +} + +void NativeWidgetGtk::SaveWindowPosition() { + // The delegate may have gone away on us. + if (!GetWidget()->widget_delegate()) + return; + + ui::WindowShowState show_state = ui::SHOW_STATE_NORMAL; + if (IsMaximized()) + show_state = ui::SHOW_STATE_MAXIMIZED; + else if (IsMinimized()) + show_state = ui::SHOW_STATE_MINIMIZED; + GetWidget()->widget_delegate()->SaveWindowPlacement( + GetWidget()->GetWindowScreenBounds(), + show_state); +} + +//////////////////////////////////////////////////////////////////////////////// +// Widget, public: + +// static +void Widget::NotifyLocaleChanged() { + GList *window_list = gtk_window_list_toplevels(); + for (GList* element = window_list; element; element = g_list_next(element)) { + Widget* widget = + Widget::GetWidgetForNativeWindow(GTK_WINDOW(element->data)); + if (widget) + widget->LocaleChanged(); + } + g_list_free(window_list); +} + +// static +void Widget::CloseAllSecondaryWidgets() { + GList* windows = gtk_window_list_toplevels(); + for (GList* window = windows; window; + window = g_list_next(window)) { + Widget* widget = Widget::GetWidgetForNativeView(GTK_WIDGET(window->data)); + if (widget && widget->is_secondary_widget()) + widget->Close(); + } + g_list_free(windows); +} + +// static +bool Widget::ConvertRect(const Widget* source, + const Widget* target, + gfx::Rect* rect) { + DCHECK(source); + DCHECK(target); + DCHECK(rect); + + // TODO(oshima): Add check if source and target belongs to the same + // screen. + + if (source == target) + return true; + if (!source || !target) + return false; + + gfx::Point source_point = source->GetWindowScreenBounds().origin(); + gfx::Point target_point = target->GetWindowScreenBounds().origin(); + + rect->set_origin( + source_point.Subtract(target_point).Add(rect->origin())); + return true; +} + +namespace internal { + +//////////////////////////////////////////////////////////////////////////////// +// NativeWidgetPrivate, public: + +// static +NativeWidgetPrivate* NativeWidgetPrivate::CreateNativeWidget( + NativeWidgetDelegate* delegate) { + return new NativeWidgetGtk(delegate); +} + +// static +NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeView( + gfx::NativeView native_view) { + if (!native_view) + return NULL; + return reinterpret_cast<NativeWidgetGtk*>( + g_object_get_data(G_OBJECT(native_view), kNativeWidgetKey)); +} + +// static +NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeWindow( + gfx::NativeWindow native_window) { + if (!native_window) + return NULL; + return reinterpret_cast<NativeWidgetGtk*>( + g_object_get_data(G_OBJECT(native_window), kNativeWidgetKey)); +} + +// static +NativeWidgetPrivate* NativeWidgetPrivate::GetTopLevelNativeWidget( + gfx::NativeView native_view) { + if (!native_view) + return NULL; + + NativeWidgetPrivate* widget = NULL; + + GtkWidget* parent_gtkwidget = native_view; + NativeWidgetPrivate* parent_widget; + do { + parent_widget = GetNativeWidgetForNativeView(parent_gtkwidget); + if (parent_widget) + widget = parent_widget; + parent_gtkwidget = gtk_widget_get_parent(parent_gtkwidget); + } while (parent_gtkwidget); + + return widget && widget->GetWidget()->is_top_level() ? widget : NULL; +} + +// static +void NativeWidgetPrivate::GetAllChildWidgets(gfx::NativeView native_view, + Widget::Widgets* children) { + if (!native_view) + return; + + Widget* widget = Widget::GetWidgetForNativeView(native_view); + if (widget) + children->insert(widget); + gtk_container_foreach(GTK_CONTAINER(native_view), + EnumerateChildWidgetsForNativeWidgets, + reinterpret_cast<gpointer>(children)); +} + +// static +void NativeWidgetPrivate::ReparentNativeView(gfx::NativeView native_view, + gfx::NativeView new_parent) { + if (!native_view) + return; + + gfx::NativeView previous_parent = gtk_widget_get_parent(native_view); + if (previous_parent == new_parent) + return; + + Widget::Widgets widgets; + GetAllChildWidgets(native_view, &widgets); + + // First notify all the widgets that they are being disassociated + // from their previous parent. + for (Widget::Widgets::iterator it = widgets.begin(); + it != widgets.end(); ++it) { + // TODO(beng): Rename this notification to NotifyNativeViewChanging() + // and eliminate the bool parameter. + (*it)->NotifyNativeViewHierarchyChanged(false, previous_parent); + } + + if (gtk_widget_get_parent(native_view)) + gtk_widget_reparent(native_view, new_parent); + else + gtk_container_add(GTK_CONTAINER(new_parent), native_view); + + // And now, notify them that they have a brand new parent. + for (Widget::Widgets::iterator it = widgets.begin(); + it != widgets.end(); ++it) { + (*it)->NotifyNativeViewHierarchyChanged(true, new_parent); + } +} + +// static +bool NativeWidgetPrivate::IsMouseButtonDown() { + bool button_pressed = false; + GdkEvent* event = gtk_get_current_event(); + if (event) { + button_pressed = event->type == GDK_BUTTON_PRESS || + event->type == GDK_2BUTTON_PRESS || + event->type == GDK_3BUTTON_PRESS; + gdk_event_free(event); + } + return button_pressed; +} + +} // namespace internal +} // namespace views |