OLD | NEW |
| (Empty) |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "ui/views/controls/native/native_view_host_gtk.h" | |
6 | |
7 #include <gtk/gtk.h> | |
8 | |
9 #include <algorithm> | |
10 | |
11 #include "base/logging.h" | |
12 #include "ui/views/controls/native/native_view_host.h" | |
13 #include "ui/views/focus/focus_manager.h" | |
14 #include "ui/views/views_delegate.h" | |
15 #include "ui/views/widget/gtk_views_fixed.h" | |
16 #include "ui/views/widget/native_widget_gtk.h" | |
17 #include "ui/views/widget/widget.h" | |
18 | |
19 namespace views { | |
20 | |
21 namespace { | |
22 static bool signal_id_initialized_ = false; | |
23 static guint focus_in_event_signal_id_; | |
24 static guint focus_out_event_signal_id_; | |
25 | |
26 //////////////////////////////////////////////////////////////////////////////// | |
27 // Utility functions to block focus signals while re-creating | |
28 // Fixed widget. | |
29 | |
30 void InitSignalIds() { | |
31 if (!signal_id_initialized_) { | |
32 signal_id_initialized_ = true; | |
33 focus_in_event_signal_id_ = | |
34 g_signal_lookup("focus-in-event", GTK_TYPE_WIDGET); | |
35 focus_out_event_signal_id_ = | |
36 g_signal_lookup("focus-out-event", GTK_TYPE_WIDGET); | |
37 } | |
38 } | |
39 | |
40 // Blocks a |signal_id| on the given |widget| if any. | |
41 void BlockSignal(GtkWidget* widget, guint signal_id) { | |
42 gulong handler_id = g_signal_handler_find(G_OBJECT(widget), | |
43 G_SIGNAL_MATCH_ID, | |
44 signal_id, | |
45 0, NULL, NULL, NULL); | |
46 if (handler_id) { | |
47 g_signal_handler_block(G_OBJECT(widget), handler_id); | |
48 } | |
49 } | |
50 | |
51 // Unblocks a |signal_id| on the given |widget| if any. | |
52 void UnblockSignal(GtkWidget* widget, guint signal_id) { | |
53 gulong handler_id = g_signal_handler_find(G_OBJECT(widget), | |
54 G_SIGNAL_MATCH_ID, | |
55 signal_id, | |
56 0, NULL, NULL, NULL); | |
57 if (handler_id) { | |
58 g_signal_handler_unblock(G_OBJECT(widget), handler_id); | |
59 } | |
60 } | |
61 | |
62 // Blocks focus in/out signals of the widget and its descendent | |
63 // children. | |
64 // Note: Due to the limiation of Gtk API, this only blocks the 1st | |
65 // handler found and won't block the rest if there is more than one handlers. | |
66 // See bug http://crbug.com/33236. | |
67 void BlockFocusSignals(GtkWidget* widget, gpointer data) { | |
68 if (!widget) | |
69 return; | |
70 InitSignalIds(); | |
71 BlockSignal(widget, focus_in_event_signal_id_); | |
72 BlockSignal(widget, focus_out_event_signal_id_); | |
73 if (GTK_IS_CONTAINER(widget)) | |
74 gtk_container_foreach(GTK_CONTAINER(widget), BlockFocusSignals, data); | |
75 } | |
76 | |
77 // Unlocks focus in/out signals of the widget and its descendent children. | |
78 void UnblockFocusSignals(GtkWidget* widget, gpointer data) { | |
79 if (!widget) | |
80 return; | |
81 InitSignalIds(); | |
82 UnblockSignal(widget, focus_in_event_signal_id_); | |
83 UnblockSignal(widget, focus_out_event_signal_id_); | |
84 if (GTK_IS_CONTAINER(widget)) | |
85 gtk_container_foreach(GTK_CONTAINER(widget), UnblockFocusSignals, data); | |
86 } | |
87 | |
88 // Removes |child| from |parent|. | |
89 void RemoveFromParent(GtkWidget* child, gpointer parent) { | |
90 gtk_container_remove(GTK_CONTAINER(parent), child); | |
91 } | |
92 | |
93 // Reparents |child| to be a child of |parent|. | |
94 void Reparent(GtkWidget* child, gpointer parent) { | |
95 gtk_widget_reparent(child, GTK_WIDGET(parent)); | |
96 } | |
97 | |
98 } // namespace | |
99 | |
100 //////////////////////////////////////////////////////////////////////////////// | |
101 // NativeViewHostGtk, public: | |
102 | |
103 NativeViewHostGtk::NativeViewHostGtk(NativeViewHost* host) | |
104 : host_(host), | |
105 installed_clip_(false), | |
106 destroy_signal_id_(0), | |
107 focus_signal_id_(0), | |
108 fixed_(NULL) { | |
109 CreateFixed(false); | |
110 } | |
111 | |
112 NativeViewHostGtk::~NativeViewHostGtk() { | |
113 if (fixed_) { | |
114 gtk_container_foreach(GTK_CONTAINER(fixed_), RemoveFromParent, fixed_); | |
115 gtk_widget_destroy(fixed_); | |
116 } | |
117 } | |
118 | |
119 //////////////////////////////////////////////////////////////////////////////// | |
120 // NativeViewHostGtk, NativeViewHostWrapper implementation: | |
121 | |
122 void NativeViewHostGtk::NativeViewAttached() { | |
123 AttachHostWidget(); | |
124 | |
125 GtkWidget* host_widget = host_->native_view(); | |
126 | |
127 // Let the widget know that the native component has been painted. | |
128 views::NativeWidgetGtk::RegisterChildExposeHandler(host_widget); | |
129 | |
130 if (!destroy_signal_id_) { | |
131 destroy_signal_id_ = g_signal_connect(host_widget, | |
132 "destroy", G_CALLBACK(CallDestroy), | |
133 this); | |
134 } | |
135 | |
136 if (!focus_signal_id_) { | |
137 focus_signal_id_ = g_signal_connect(host_widget, | |
138 "focus-in-event", | |
139 G_CALLBACK(CallFocusIn), this); | |
140 } | |
141 | |
142 // Always layout though. | |
143 host_->Layout(); | |
144 | |
145 // TODO(port): figure out focus. | |
146 } | |
147 | |
148 void NativeViewHostGtk::NativeViewDetaching(bool destroyed) { | |
149 GtkWidget* host_widget = host_->native_view(); | |
150 DCHECK(host_widget); | |
151 | |
152 views::NativeWidgetGtk::UnregisterChildExposeHandler(host_widget); | |
153 | |
154 g_signal_handler_disconnect(G_OBJECT(host_widget), destroy_signal_id_); | |
155 destroy_signal_id_ = 0; | |
156 | |
157 g_signal_handler_disconnect(G_OBJECT(host_widget), focus_signal_id_); | |
158 focus_signal_id_ = 0; | |
159 | |
160 installed_clip_ = false; | |
161 } | |
162 | |
163 void NativeViewHostGtk::AddedToWidget() { | |
164 if (!fixed_) | |
165 CreateFixed(false); | |
166 if (gtk_widget_get_parent(fixed_)) | |
167 GetHostWidget()->ReparentChild(fixed_); | |
168 else | |
169 GetHostWidget()->AddChild(fixed_); | |
170 | |
171 if (!host_->native_view()) | |
172 return; | |
173 | |
174 AttachHostWidget(); | |
175 | |
176 if (host_->IsDrawn()) { | |
177 gtk_widget_show(host_->native_view()); | |
178 gtk_widget_show(fixed_); | |
179 } else { | |
180 gtk_widget_hide(fixed_); | |
181 } | |
182 host_->Layout(); | |
183 } | |
184 | |
185 void NativeViewHostGtk::RemovedFromWidget() { | |
186 if (!host_->native_view()) | |
187 return; | |
188 DestroyFixed(); | |
189 } | |
190 | |
191 void NativeViewHostGtk::InstallClip(int x, int y, int w, int h) { | |
192 DCHECK(w > 0 && h > 0); | |
193 installed_clip_bounds_.SetRect(x, y, w, h); | |
194 if (!installed_clip_) { | |
195 installed_clip_ = true; | |
196 | |
197 // We only re-create the fixed with a window when a cliprect is installed. | |
198 // Because the presence of a X Window will prevent transparency from working | |
199 // properly, we only want it to be active for the duration of a clip | |
200 // (typically during animations and scrolling.) | |
201 CreateFixed(true); | |
202 } | |
203 } | |
204 | |
205 bool NativeViewHostGtk::HasInstalledClip() { | |
206 return installed_clip_; | |
207 } | |
208 | |
209 void NativeViewHostGtk::UninstallClip() { | |
210 installed_clip_ = false; | |
211 // We now re-create the fixed without a X Window so transparency works again. | |
212 CreateFixed(false); | |
213 } | |
214 | |
215 void NativeViewHostGtk::ShowWidget(int x, int y, int w, int h) { | |
216 // x and y are the desired position of host_ in NativeWidgetGtk coordinates. | |
217 int fixed_x = x; | |
218 int fixed_y = y; | |
219 int fixed_w = w; | |
220 int fixed_h = h; | |
221 int child_x = 0; | |
222 int child_y = 0; | |
223 int child_w = w; | |
224 int child_h = h; | |
225 if (installed_clip_) { | |
226 child_x = -installed_clip_bounds_.x(); | |
227 child_y = -installed_clip_bounds_.y(); | |
228 fixed_x += -child_x; | |
229 fixed_y += -child_y; | |
230 fixed_w = std::min(installed_clip_bounds_.width(), w); | |
231 fixed_h = std::min(installed_clip_bounds_.height(), h); | |
232 } | |
233 | |
234 GtkWidget* host_widget = host_->native_view(); | |
235 // Don't call gtk_widget_size_allocate now, as we're possibly in the | |
236 // middle of a re-size, and it kicks off another re-size, and you | |
237 // get flashing. Instead, we'll set the desired size as properties | |
238 // on the widget and queue the re-size. | |
239 gtk_views_fixed_set_widget_size(host_widget, child_w, child_h); | |
240 gtk_fixed_move(GTK_FIXED(fixed_), host_widget, child_x, child_y); | |
241 | |
242 // Size and place the fixed_. | |
243 GetHostWidget()->PositionChild(fixed_, fixed_x, fixed_y, fixed_w, fixed_h); | |
244 | |
245 gtk_widget_show(host_widget); | |
246 gtk_widget_show(fixed_); | |
247 } | |
248 | |
249 void NativeViewHostGtk::HideWidget() { | |
250 if (fixed_) | |
251 gtk_widget_hide(fixed_); | |
252 } | |
253 | |
254 void NativeViewHostGtk::SetFocus() { | |
255 GtkWidget* host_widget = host_->native_view(); | |
256 DCHECK(host_widget); | |
257 gtk_widget_grab_focus(host_widget); | |
258 } | |
259 | |
260 gfx::NativeViewAccessible NativeViewHostGtk::GetNativeViewAccessible() { | |
261 return NULL; | |
262 } | |
263 | |
264 //////////////////////////////////////////////////////////////////////////////// | |
265 // NativeViewHostGtk, private: | |
266 | |
267 void NativeViewHostGtk::CreateFixed(bool needs_window) { | |
268 GtkWidget* focused_widget = GetFocusedDescendant(); | |
269 | |
270 bool focus_event_blocked = false; | |
271 // We move focus around and do not want focus events to be emitted | |
272 // during this process. | |
273 if (fixed_) { | |
274 BlockFocusSignals(GetHostWidget()->GetNativeView(), NULL); | |
275 focus_event_blocked = true; | |
276 } | |
277 | |
278 if (focused_widget) { | |
279 // A descendant of our fixed has focus. When we destroy the fixed focus is | |
280 // automatically moved. Temporarily move focus to our host widget, then | |
281 // restore focus after we create the new fixed_. This way focus hasn't | |
282 // really moved. | |
283 gtk_widget_grab_focus(GetHostWidget()->GetNativeView()); | |
284 } | |
285 | |
286 // Move all the contained widgets to the new fixed. | |
287 GtkWidget* new_fixed = gtk_views_fixed_new(); | |
288 if (fixed_) { | |
289 gtk_container_foreach(GTK_CONTAINER(fixed_), Reparent, new_fixed); | |
290 DestroyFixed(); | |
291 } | |
292 fixed_ = new_fixed; | |
293 | |
294 gtk_widget_set_name(fixed_, "views-native-view-host-fixed"); | |
295 gtk_fixed_set_has_window(GTK_FIXED(fixed_), needs_window); | |
296 | |
297 // Defeat refcounting. We need to own the fixed. | |
298 gtk_widget_ref(fixed_); | |
299 | |
300 NativeWidgetGtk* widget_gtk = GetHostWidget(); | |
301 if (widget_gtk) { | |
302 widget_gtk->AddChild(fixed_); | |
303 // Clear the background so we don't get flicker. | |
304 gtk_widget_realize(fixed_); | |
305 gdk_window_set_back_pixmap(fixed_->window, NULL, false); | |
306 } | |
307 | |
308 if (host_->native_view()) | |
309 AttachHostWidget(); | |
310 | |
311 if (widget_gtk && host_->native_view() && focused_widget) | |
312 gtk_widget_grab_focus(focused_widget); | |
313 | |
314 if (focus_event_blocked) { | |
315 // Unblocking a signal handler that is not blocked fails. | |
316 // Unblock only when it's unblocked. | |
317 UnblockFocusSignals(GetHostWidget()->GetNativeView(), NULL); | |
318 } | |
319 } | |
320 | |
321 void NativeViewHostGtk::DestroyFixed() { | |
322 if (!fixed_) | |
323 return; | |
324 | |
325 gtk_widget_hide(fixed_); | |
326 gtk_container_foreach(GTK_CONTAINER(fixed_), RemoveFromParent, fixed_); | |
327 GetHostWidget()->RemoveChild(fixed_); | |
328 | |
329 // fixed_ should not have any children this point. | |
330 DCHECK_EQ(0U, | |
331 g_list_length(gtk_container_get_children(GTK_CONTAINER(fixed_)))); | |
332 gtk_widget_destroy(fixed_); | |
333 fixed_ = NULL; | |
334 } | |
335 | |
336 NativeWidgetGtk* NativeViewHostGtk::GetHostWidget() const { | |
337 return static_cast<NativeWidgetGtk*>(host_->GetWidget()->native_widget()); | |
338 } | |
339 | |
340 GtkWidget* NativeViewHostGtk::GetFocusedDescendant() { | |
341 if (!fixed_) | |
342 return NULL; | |
343 NativeWidgetGtk* host = GetHostWidget(); | |
344 if (!host) | |
345 return NULL; | |
346 GtkWidget* top_level = gtk_widget_get_toplevel(host->GetNativeView()); | |
347 if (!top_level || !GTK_IS_WINDOW(top_level)) | |
348 return NULL; | |
349 GtkWidget* focused = gtk_window_get_focus(GTK_WINDOW(top_level)); | |
350 if (!focused) | |
351 return NULL; | |
352 return (focused == fixed_ || gtk_widget_is_ancestor(focused, fixed_)) ? | |
353 focused : NULL; | |
354 } | |
355 | |
356 void NativeViewHostGtk::AttachHostWidget() { | |
357 GtkWidget* host_widget = host_->native_view(); | |
358 DCHECK(host_widget); | |
359 | |
360 GtkWidget* host_parent = gtk_widget_get_parent(host_widget); | |
361 bool parent_changed = true; | |
362 if (host_parent) { | |
363 if (host_parent != fixed_) | |
364 gtk_widget_reparent(host_widget, fixed_); | |
365 else | |
366 parent_changed = false; | |
367 } else { | |
368 gtk_container_add(GTK_CONTAINER(fixed_), host_widget); | |
369 } | |
370 | |
371 if (parent_changed) { | |
372 // We need to clear the background so we don't get flicker on tab switching. | |
373 // To do that we must realize the widget if it's not already. | |
374 if (!GTK_WIDGET_REALIZED(host_widget)) | |
375 gtk_widget_realize(host_widget); | |
376 gdk_window_set_back_pixmap(host_widget->window, NULL, false); | |
377 } | |
378 } | |
379 | |
380 // static | |
381 void NativeViewHostGtk::CallDestroy(GtkObject* object, | |
382 NativeViewHostGtk* host) { | |
383 host->host_->NativeViewDestroyed(); | |
384 } | |
385 | |
386 // static | |
387 gboolean NativeViewHostGtk::CallFocusIn(GtkWidget* gtk_widget, | |
388 GdkEventFocus* event, | |
389 NativeViewHostGtk* host) { | |
390 Widget* widget = Widget::GetWidgetForNativeView(gtk_widget); | |
391 FocusManager* focus_manager = widget ? widget->GetFocusManager() : NULL; | |
392 if (!focus_manager) { | |
393 // TODO(jcampan): http://crbug.com/21378 Reenable this NOTREACHED() when the | |
394 // options page is only based on views. | |
395 // NOTREACHED(); | |
396 NOTIMPLEMENTED(); | |
397 return false; | |
398 } | |
399 focus_manager->SetFocusedView(host->host_->focus_view()); | |
400 return false; | |
401 } | |
402 | |
403 //////////////////////////////////////////////////////////////////////////////// | |
404 // NativeViewHostWrapper, public: | |
405 | |
406 // static | |
407 NativeViewHostWrapper* NativeViewHostWrapper::CreateWrapper( | |
408 NativeViewHost* host) { | |
409 return new NativeViewHostGtk(host); | |
410 } | |
411 | |
412 } // namespace views | |
OLD | NEW |