// 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 "views/widget/native_widget_gtk.h" #include #include #include #include #include #include #include #include "base/auto_reset.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_windowing.h" #include "ui/base/gtk/scoped_handle_gtk.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 "views/controls/textfield/native_textfield_views.h" #include "views/focus/view_storage.h" #include "views/ime/input_method_gtk.h" #include "views/views_delegate.h" #include "views/widget/drop_target_gtk.h" #include "views/widget/gtk_views_fixed.h" #include "views/widget/gtk_views_window.h" #include "views/widget/native_widget_views.h" #include "views/widget/root_view.h" #include "views/widget/widget_delegate.h" #include "views/window/hit_test.h" #if defined(TOUCH_UI) #include "views/widget/tooltip_manager_views.h" #include "views/touchui/touch_factory.h" #else #include "views/widget/tooltip_manager_gtk.h" #endif #if defined(HAVE_IBUS) #include "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( 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("")); } // 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(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( 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(param); widgets->insert(widget); } } void RemoveExposeHandlerIfExists(GtkWidget* widget) { gulong id = reinterpret_cast(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_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::get(); } 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) { } private: NativeWidgetGtk* GetNativeWidgetGtkForEvent(GdkEvent* event) { GtkWidget* gtk_widget = gtk_get_event_widget(event); if (!gtk_widget) return NULL; return static_cast( 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(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) { #if defined(TOUCH_UI) // Make sure the touch factory is initialized so that it can setup XInput2 for // the widget. 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. input_method_.reset(); if (ownership_ == Widget::InitParams::NATIVE_WIDGET_OWNS_WIDGET) { DCHECK(widget_ == NULL); delete delegate_; } else { CloseNow(); } } 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(data.provider()); GtkTargetList* targets = data_provider.GetTargetList(); GdkEvent* current_event = gtk_get_current_event(); const OSExchangeDataProviderGtk& provider( static_cast(data.provider())); GdkDragContext* context = gtk_drag_begin( window_contents_, targets, static_cast( 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()->Run(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(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. 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(&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(id)); } //////////////////////////////////////////////////////////////////////////////// // NativeWidgetGtk, NativeWidget implementation: void NativeWidgetGtk::InitNativeWidget(const Widget::InitParams& params) { SetInitParams(params); Widget::InitParams modified_params = params; gfx::NativeView parent = params.parent; if (params.parent_widget) { NativeWidgetGtk* parent_gtk = static_cast(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 (Widget::compositor_factory()) { compositor_ = (*Widget::compositor_factory())(); } else { gint width, height; gdk_drawable_get_size(window_contents_->window, &width, &height); compositor_ = ui::Compositor::Create( GDK_WINDOW_XID(window_contents_->window), gfx::Size(width, height)); } if (compositor_.get()) delegate_->AsWidget()->GetRootView()->SetPaintToLayer(true); } 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); g_signal_connect_after(G_OBJECT(window_contents_), "size_request", G_CALLBACK(&OnSizeRequestThunk), this); g_signal_connect_after(G_OBJECT(window_contents_), "size_allocate", G_CALLBACK(&OnSizeAllocateThunk), this); gtk_widget_set_app_paintable(window_contents_, true); g_signal_connect(window_contents_, "expose_event", G_CALLBACK(&OnPaintThunk), this); g_signal_connect(window_contents_, "enter_notify_event", G_CALLBACK(&OnEnterNotifyThunk), this); g_signal_connect(window_contents_, "leave_notify_event", G_CALLBACK(&OnLeaveNotifyThunk), this); g_signal_connect(window_contents_, "motion_notify_event", G_CALLBACK(&OnMotionNotifyThunk), this); g_signal_connect(window_contents_, "button_press_event", G_CALLBACK(&OnButtonPressThunk), this); g_signal_connect(window_contents_, "button_release_event", G_CALLBACK(&OnButtonReleaseThunk), this); g_signal_connect(window_contents_, "grab_broken_event", G_CALLBACK(&OnGrabBrokeEventThunk), this); g_signal_connect(window_contents_, "scroll_event", G_CALLBACK(&OnScrollThunk), this); g_signal_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. g_signal_connect(widget_, "focus_in_event", G_CALLBACK(&OnFocusInThunk), this); g_signal_connect(widget_, "focus_out_event", G_CALLBACK(&OnFocusOutThunk), this); g_signal_connect(widget_, "destroy", G_CALLBACK(&OnDestroyThunk), this); g_signal_connect(widget_, "show", G_CALLBACK(&OnShowThunk), this); g_signal_connect(widget_, "map", G_CALLBACK(&OnMapThunk), this); g_signal_connect(widget_, "hide", G_CALLBACK(&OnHideThunk), this); g_signal_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. g_signal_connect(widget_, "key_press_event", G_CALLBACK(&OnEventKeyThunk), this); g_signal_connect(widget_, "key_release_event", G_CALLBACK(&OnEventKeyThunk), this); // Drag and drop. gtk_drag_dest_set(window_contents_, static_cast(0), NULL, 0, GDK_ACTION_COPY); g_signal_connect(window_contents_, "drag_motion", G_CALLBACK(&OnDragMotionThunk), this); g_signal_connect(window_contents_, "drag_data_received", G_CALLBACK(&OnDragDataReceivedThunk), this); g_signal_connect(window_contents_, "drag_drop", G_CALLBACK(&OnDragDropThunk), this); g_signal_connect(window_contents_, "drag_leave", G_CALLBACK(&OnDragLeaveThunk), this); g_signal_connect(window_contents_, "drag_data_get", G_CALLBACK(&OnDragDataGetThunk), this); g_signal_connect(window_contents_, "drag_end", G_CALLBACK(&OnDragEndThunk), this); g_signal_connect(window_contents_, "drag_failed", G_CALLBACK(&OnDragFailedThunk), this); g_signal_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(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); g_signal_connect(window_contents_, "query_tooltip", G_CALLBACK(&OnQueryTooltipThunk), this); if (child_) { if (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::MarkLayerDirty() { } void NativeWidgetGtk::CalculateOffsetToAncestorWithLayer(gfx::Point* offset, View** ancestor) { } 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( 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 = 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() { 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) TouchFactory::GetInstance()->UngrabTouchDevices( GDK_WINDOW_XDISPLAY(window_contents()->window)); #endif delegate_->OnMouseCaptureLost(); } } bool NativeWidgetGtk::HasMouseCapture() const { return GTK_WIDGET_HAS_GRAB(window_contents_) || has_pointer_grab_; } InputMethod* NativeWidgetGtk::GetInputMethodNative() { if (!input_method_.get()) { // Create input method when it is requested by a child view. // TODO(suzhe): Always enable input method when we start to use // RenderWidgetHostViewViews in normal ChromeOS. if (!child_ && views::Widget::IsPureViews()) { #if defined(HAVE_IBUS) input_method_.reset(InputMethodIBus::IsInputMethodIBusEnabled() ? static_cast(new InputMethodIBus(this)) : static_cast(new InputMethodGtk(this))); #else input_method_.reset(new InputMethodGtk(this)); #endif input_method_->Init(GetWidget()); if (has_focus_) input_method_->OnFocus(); } } return input_method_.get(); } void NativeWidgetGtk::ReplaceInputMethod(InputMethod* input_method) { input_method_.reset(input_method); if (input_method) { input_method->set_delegate(this); input_method->Init(GetWidget()); } } 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()); SetBoundsConstrained(bounds, NULL); } void NativeWidgetGtk::GetWindowBoundsAndMaximizedState(gfx::Rect* bounds, bool* maximized) const { // Do nothing for now. ChromeOS isn't yet saving window placement. } void NativeWidgetGtk::SetWindowTitle(const std::wstring& 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 std::wstring& 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( 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::SetBoundsConstrained(const gfx::Rect& bounds, Widget* other_widget) { // We apparently don't care about |other_widget|. SetBounds(bounds); } 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_.empty()) { // And we delay the close just in case we're on the stack. MessageLoop::current()->PostTask(FROM_HERE, close_widget_factory_.NewRunnableMethod( &NativeWidgetGtk::CloseNow)); } } void NativeWidgetGtk::CloseNow() { if (widget_) { input_method_.reset(); 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); delegate_->OnNativeWidgetVisibilityChanged(true); } } void NativeWidgetGtk::Hide() { if (widget_) { gtk_widget_hide(widget_); if (widget_->window) gdk_window_lower(widget_->window); delegate_->OnNativeWidgetVisibilityChanged(false); } } void NativeWidgetGtk::ShowMaximizedWithBounds( const gfx::Rect& restored_bounds) { // TODO: when we add maximization support update this. Show(); } void NativeWidgetGtk::ShowWithState(ShowState state) { // No concept of maximization (yet) on ChromeOS. if (state == internal::NativeWidgetPrivate::SHOW_INACTIVE) gtk_window_set_focus_on_map(GetNativeWindow(), false); gtk_widget_show(GetNativeView()); } bool NativeWidgetGtk::IsVisible() const { return GTK_WIDGET_VISIBLE(GetNativeView()); } 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() { gtk_window_maximize(GetNativeWindow()); } 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(opacity) / static_cast(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 (TouchFactory::GetInstance()->keep_mouse_cursor()) cursor = gfx::GetCursor(GDK_ARROW); else if (!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); } //////////////////////////////////////////////////////////////////////////////// // 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_->OnWidgetSizeChanged(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(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(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 (child_) return false; // Only top-level Widget should have an InputMethod instance. if (input_method_.get()) 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 (child_) return false; // Only top-level Widget should have an InputMethod instance. if (input_method_.get()) input_method_->OnBlur(); return false; } gboolean NativeWidgetGtk::OnEventKey(GtkWidget* widget, GdkEventKey* event) { KeyEvent key(reinterpret_cast(event)); if (input_method_.get()) 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(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(HAVE_XINPUT2) && defined(TOUCH_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) { 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) { } 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) { } 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::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(key.native_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(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(widget_, "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); } } else { // Use our own window class to override GtkWindow's move_focus method. widget_ = gtk_views_window_new(WindowTypeToGtkWindowType(params.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(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); g_signal_connect(widget_, "expose_event", G_CALLBACK(&OnWindowPaintThunk), this); gtk_widget_realize(widget_); gdk_window_set_decorations(widget_->window, static_cast(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; bool maximized = window_state_ & GDK_WINDOW_STATE_MAXIMIZED; GetWidget()->widget_delegate()->SaveWindowPlacement( GetWidget()->GetWindowScreenBounds(), maximized); } //////////////////////////////////////////////////////////////////////////////// // 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); GtkWidget* source_widget = source->GetNativeView(); GtkWidget* target_widget = target->GetNativeView(); if (source_widget == target_widget) return true; if (!source_widget || !target_widget) return false; GdkRectangle gdk_rect = rect->ToGdkRectangle(); if (gtk_widget_translate_coordinates(source_widget, target_widget, gdk_rect.x, gdk_rect.y, &gdk_rect.x, &gdk_rect.y)) { *rect = gdk_rect; return true; } return false; } namespace internal { //////////////////////////////////////////////////////////////////////////////// // NativeWidgetPrivate, public: // static NativeWidgetPrivate* NativeWidgetPrivate::CreateNativeWidget( NativeWidgetDelegate* delegate) { if (Widget::IsPureViews() && ViewsDelegate::views_delegate->GetDefaultParentView()) { return new NativeWidgetViews(delegate); } return new NativeWidgetGtk(delegate); } // static NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeView( gfx::NativeView native_view) { if (!native_view) return NULL; return reinterpret_cast( 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( 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; } // 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(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