]> git.saurik.com Git - wxWidgets.git/blob - src/gtk/win_gtk.cpp
follow up parent chain to properly support modal dialog parents, see #15383
[wxWidgets.git] / src / gtk / win_gtk.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/win_gtk.cpp
3 // Purpose: native GTK+ widget for wxWindow
4 // Author: Paul Cornett
5 // Copyright: (c) 2007 Paul Cornett
6 // Licence: wxWindows licence
7 ///////////////////////////////////////////////////////////////////////////////
8
9 #include "wx/wxprec.h"
10
11 #include "wx/defs.h"
12
13 #include <gtk/gtk.h>
14 #include "wx/gtk/private/win_gtk.h"
15
16 #include "wx/gtk/private.h"
17 #include "wx/gtk/private/gtk2-compat.h"
18
19 /*
20 wxPizza is a custom GTK+ widget derived from GtkFixed. A custom widget
21 is needed to adapt GTK+ to wxWidgets needs in 3 areas: scrolling, window
22 borders, and RTL.
23
24 For scrolling, the "set_scroll_adjustments" signal is implemented
25 to make wxPizza appear scrollable to GTK+, allowing it to be put in a
26 GtkScrolledWindow. Child widget positions are adjusted for the scrolling
27 position in size_allocate.
28
29 For borders, space is reserved in realize and size_allocate. The border is
30 drawn on wxPizza's parent GdkWindow.
31
32 For RTL, child widget positions are mirrored in size_allocate.
33 */
34
35 struct wxPizzaChild
36 {
37 GtkWidget* widget;
38 int x, y, width, height;
39 };
40
41 static GtkWidgetClass* parent_class;
42
43 #ifdef __WXGTK3__
44 enum {
45 PROP_0,
46 PROP_HADJUSTMENT,
47 PROP_VADJUSTMENT,
48 PROP_HSCROLL_POLICY,
49 PROP_VSCROLL_POLICY
50 };
51 #endif
52
53 extern "C" {
54
55 struct wxPizzaClass
56 {
57 GtkFixedClass parent;
58 #ifndef __WXGTK3__
59 void (*set_scroll_adjustments)(GtkWidget*, GtkAdjustment*, GtkAdjustment*);
60 #endif
61 };
62
63 static void pizza_size_allocate(GtkWidget* widget, GtkAllocation* alloc)
64 {
65 wxPizza* pizza = WX_PIZZA(widget);
66 GtkBorder border;
67 pizza->get_border(border);
68 int w = alloc->width - border.left - border.right;
69 if (w < 0) w = 0;
70
71 if (gtk_widget_get_realized(widget))
72 {
73 int h = alloc->height - border.top - border.bottom;
74 if (h < 0) h = 0;
75 const int x = alloc->x + border.left;
76 const int y = alloc->y + border.top;
77
78 GdkWindow* window = gtk_widget_get_window(widget);
79 int old_x, old_y;
80 gdk_window_get_position(window, &old_x, &old_y);
81
82 if (x != old_x || y != old_y ||
83 w != gdk_window_get_width(window) || h != gdk_window_get_height(window))
84 {
85 gdk_window_move_resize(window, x, y, w, h);
86
87 if (border.left + border.right + border.top + border.bottom)
88 {
89 // old and new border areas need to be invalidated,
90 // otherwise they will not be erased/redrawn properly
91 GtkAllocation old_alloc;
92 gtk_widget_get_allocation(widget, &old_alloc);
93 GdkWindow* parent = gtk_widget_get_parent_window(widget);
94 gdk_window_invalidate_rect(parent, &old_alloc, false);
95 gdk_window_invalidate_rect(parent, alloc, false);
96 }
97 }
98 }
99
100 gtk_widget_set_allocation(widget, alloc);
101
102 // adjust child positions
103 for (const GList* p = pizza->m_children; p; p = p->next)
104 {
105 const wxPizzaChild* child = static_cast<wxPizzaChild*>(p->data);
106 if (gtk_widget_get_visible(child->widget))
107 {
108 GtkAllocation child_alloc;
109 // note that child positions do not take border into
110 // account, they need to be relative to widget->window,
111 // which has already been adjusted
112 child_alloc.x = child->x - pizza->m_scroll_x;
113 child_alloc.y = child->y - pizza->m_scroll_y;
114 child_alloc.width = child->width;
115 child_alloc.height = child->height;
116 if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL)
117 child_alloc.x = w - child_alloc.x - child_alloc.width;
118 gtk_widget_size_allocate(child->widget, &child_alloc);
119 }
120 }
121 }
122
123 static void pizza_realize(GtkWidget* widget)
124 {
125 parent_class->realize(widget);
126
127 wxPizza* pizza = WX_PIZZA(widget);
128 if (pizza->m_windowStyle & wxPizza::BORDER_STYLES)
129 {
130 GtkBorder border;
131 pizza->get_border(border);
132 GtkAllocation a;
133 gtk_widget_get_allocation(widget, &a);
134 int x = a.x + border.left;
135 int y = a.y + border.top;
136 int w = a.width - border.left - border.right;
137 int h = a.height - border.top - border.bottom;
138 if (w < 0) w = 0;
139 if (h < 0) h = 0;
140 gdk_window_move_resize(gtk_widget_get_window(widget), x, y, w, h);
141 }
142 }
143
144 static void pizza_show(GtkWidget* widget)
145 {
146 GtkWidget* parent = gtk_widget_get_parent(widget);
147 if (parent && (WX_PIZZA(widget)->m_windowStyle & wxPizza::BORDER_STYLES))
148 {
149 // invalidate whole allocation so borders will be drawn properly
150 GtkAllocation a;
151 gtk_widget_get_allocation(widget, &a);
152 gtk_widget_queue_draw_area(parent, a.x, a.y, a.width, a.height);
153 }
154
155 parent_class->show(widget);
156 }
157
158 static void pizza_hide(GtkWidget* widget)
159 {
160 GtkWidget* parent = gtk_widget_get_parent(widget);
161 if (parent && (WX_PIZZA(widget)->m_windowStyle & wxPizza::BORDER_STYLES))
162 {
163 // invalidate whole allocation so borders will be erased properly
164 GtkAllocation a;
165 gtk_widget_get_allocation(widget, &a);
166 gtk_widget_queue_draw_area(parent, a.x, a.y, a.width, a.height);
167 }
168
169 parent_class->hide(widget);
170 }
171
172 static void pizza_add(GtkContainer* container, GtkWidget* widget)
173 {
174 WX_PIZZA(container)->put(widget, 0, 0, 1, 1);
175 }
176
177 static void pizza_remove(GtkContainer* container, GtkWidget* widget)
178 {
179 GTK_CONTAINER_CLASS(parent_class)->remove(container, widget);
180
181 wxPizza* pizza = WX_PIZZA(container);
182 for (GList* p = pizza->m_children; p; p = p->next)
183 {
184 wxPizzaChild* child = static_cast<wxPizzaChild*>(p->data);
185 if (child->widget == widget)
186 {
187 pizza->m_children = g_list_delete_link(pizza->m_children, p);
188 delete child;
189 break;
190 }
191 }
192 }
193
194 #ifdef __WXGTK3__
195 static void pizza_get_preferred_width(GtkWidget* widget, int* minimum, int* natural)
196 {
197 *minimum = 0;
198 gtk_widget_get_size_request(widget, natural, NULL);
199 if (*natural < 0)
200 *natural = 0;
201 }
202
203 static void pizza_get_preferred_height(GtkWidget* widget, int* minimum, int* natural)
204 {
205 *minimum = 0;
206 gtk_widget_get_size_request(widget, NULL, natural);
207 if (*natural < 0)
208 *natural = 0;
209 }
210
211 // Needed to implement GtkScrollable interface, but we don't care about the
212 // properties. wxWindowGTK handles the adjustments and scroll policy.
213 static void pizza_get_property(GObject*, guint, GValue*, GParamSpec*)
214 {
215 }
216
217 static void pizza_set_property(GObject*, guint, const GValue*, GParamSpec*)
218 {
219 }
220 #else
221 // not used, but needs to exist so gtk_widget_set_scroll_adjustments will work
222 static void pizza_set_scroll_adjustments(GtkWidget*, GtkAdjustment*, GtkAdjustment*)
223 {
224 }
225
226 // Marshaller needed for set_scroll_adjustments signal,
227 // generated with GLib-2.4.6 glib-genmarshal
228 #define g_marshal_value_peek_object(v) g_value_get_object (v)
229 static void
230 g_cclosure_user_marshal_VOID__OBJECT_OBJECT (GClosure *closure,
231 GValue * /*return_value*/,
232 guint n_param_values,
233 const GValue *param_values,
234 gpointer /*invocation_hint*/,
235 gpointer marshal_data)
236 {
237 typedef void (*GMarshalFunc_VOID__OBJECT_OBJECT) (gpointer data1,
238 gpointer arg_1,
239 gpointer arg_2,
240 gpointer data2);
241 register GMarshalFunc_VOID__OBJECT_OBJECT callback;
242 register GCClosure *cc = (GCClosure*) closure;
243 register gpointer data1, data2;
244
245 g_return_if_fail (n_param_values == 3);
246
247 if (G_CCLOSURE_SWAP_DATA (closure))
248 {
249 data1 = closure->data;
250 data2 = g_value_peek_pointer (param_values + 0);
251 }
252 else
253 {
254 data1 = g_value_peek_pointer (param_values + 0);
255 data2 = closure->data;
256 }
257 callback = (GMarshalFunc_VOID__OBJECT_OBJECT) (marshal_data ? marshal_data : cc->callback);
258
259 callback (data1,
260 g_marshal_value_peek_object (param_values + 1),
261 g_marshal_value_peek_object (param_values + 2),
262 data2);
263 }
264 #endif
265
266 static void class_init(void* g_class, void*)
267 {
268 GtkWidgetClass* widget_class = (GtkWidgetClass*)g_class;
269 widget_class->size_allocate = pizza_size_allocate;
270 widget_class->realize = pizza_realize;
271 widget_class->show = pizza_show;
272 widget_class->hide = pizza_hide;
273 GtkContainerClass* container_class = (GtkContainerClass*)g_class;
274 container_class->add = pizza_add;
275 container_class->remove = pizza_remove;
276
277 #ifdef __WXGTK3__
278 widget_class->get_preferred_width = pizza_get_preferred_width;
279 widget_class->get_preferred_height = pizza_get_preferred_height;
280 GObjectClass *gobject_class = G_OBJECT_CLASS(g_class);
281 gobject_class->set_property = pizza_set_property;
282 gobject_class->get_property = pizza_get_property;
283 g_object_class_override_property(gobject_class, PROP_HADJUSTMENT, "hadjustment");
284 g_object_class_override_property(gobject_class, PROP_VADJUSTMENT, "vadjustment");
285 g_object_class_override_property(gobject_class, PROP_HSCROLL_POLICY, "hscroll-policy");
286 g_object_class_override_property(gobject_class, PROP_VSCROLL_POLICY, "vscroll-policy");
287 #else
288 wxPizzaClass* klass = static_cast<wxPizzaClass*>(g_class);
289 // needed to make widget appear scrollable to GTK+
290 klass->set_scroll_adjustments = pizza_set_scroll_adjustments;
291 widget_class->set_scroll_adjustments_signal =
292 g_signal_new(
293 "set_scroll_adjustments",
294 G_TYPE_FROM_CLASS(g_class),
295 G_SIGNAL_RUN_LAST,
296 G_STRUCT_OFFSET(wxPizzaClass, set_scroll_adjustments),
297 NULL, NULL,
298 g_cclosure_user_marshal_VOID__OBJECT_OBJECT,
299 G_TYPE_NONE, 2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT);
300 #endif
301 parent_class = GTK_WIDGET_CLASS(g_type_class_peek_parent(g_class));
302 }
303
304 } // extern "C"
305
306 GType wxPizza::type()
307 {
308 static GType type;
309 if (type == 0)
310 {
311 const GTypeInfo info = {
312 sizeof(wxPizzaClass),
313 NULL, NULL,
314 class_init,
315 NULL, NULL,
316 sizeof(wxPizza), 0,
317 NULL, NULL
318 };
319 type = g_type_register_static(
320 GTK_TYPE_FIXED, "wxPizza", &info, GTypeFlags(0));
321 #ifdef __WXGTK3__
322 const GInterfaceInfo interface_info = { NULL, NULL, NULL };
323 g_type_add_interface_static(type, GTK_TYPE_SCROLLABLE, &interface_info);
324 #endif
325 }
326 return type;
327 }
328
329 GtkWidget* wxPizza::New(long windowStyle)
330 {
331 GtkWidget* widget = GTK_WIDGET(g_object_new(type(), NULL));
332 wxPizza* pizza = WX_PIZZA(widget);
333 pizza->m_children = NULL;
334 pizza->m_scroll_x = 0;
335 pizza->m_scroll_y = 0;
336 pizza->m_windowStyle = windowStyle;
337 #ifdef __WXGTK3__
338 gtk_widget_set_has_window(widget, true);
339 #else
340 gtk_fixed_set_has_window(GTK_FIXED(widget), true);
341 #endif
342 gtk_widget_add_events(widget,
343 GDK_EXPOSURE_MASK |
344 GDK_SCROLL_MASK |
345 GDK_POINTER_MOTION_MASK |
346 GDK_POINTER_MOTION_HINT_MASK |
347 GDK_BUTTON_MOTION_MASK |
348 GDK_BUTTON1_MOTION_MASK |
349 GDK_BUTTON2_MOTION_MASK |
350 GDK_BUTTON3_MOTION_MASK |
351 GDK_BUTTON_PRESS_MASK |
352 GDK_BUTTON_RELEASE_MASK |
353 GDK_KEY_PRESS_MASK |
354 GDK_KEY_RELEASE_MASK |
355 GDK_ENTER_NOTIFY_MASK |
356 GDK_LEAVE_NOTIFY_MASK |
357 GDK_FOCUS_CHANGE_MASK);
358 return widget;
359 }
360
361 void wxPizza::move(GtkWidget* widget, int x, int y, int width, int height)
362 {
363 for (const GList* p = m_children; p; p = p->next)
364 {
365 wxPizzaChild* child = static_cast<wxPizzaChild*>(p->data);
366 if (child->widget == widget)
367 {
368 child->x = x;
369 child->y = y;
370 child->width = width;
371 child->height = height;
372 // normally a queue-resize would be needed here, but we know
373 // wxWindowGTK::DoMoveWindow() will take care of it
374 break;
375 }
376 }
377 }
378
379 void wxPizza::put(GtkWidget* widget, int x, int y, int width, int height)
380 {
381 // Re-parenting a TLW under a child window is possible at wx level but
382 // using a TLW as child at GTK+ level results in problems, so don't do it.
383 #if GTK_CHECK_VERSION(2,19,3)
384 if (!gtk_widget_is_toplevel(GTK_WIDGET(widget)))
385 #else
386 if (!GTK_WIDGET_TOPLEVEL(GTK_WIDGET(widget)))
387 #endif
388 gtk_fixed_put(GTK_FIXED(this), widget, 0, 0);
389
390 wxPizzaChild* child = new wxPizzaChild;
391 child->widget = widget;
392 child->x = x;
393 child->y = y;
394 child->width = width;
395 child->height = height;
396 m_children = g_list_append(m_children, child);
397 }
398
399 struct AdjustData {
400 GdkWindow* window;
401 int dx, dy;
402 };
403
404 // Adjust allocations for all widgets using the GdkWindow which was just scrolled
405 extern "C" {
406 static void scroll_adjust(GtkWidget* widget, void* data)
407 {
408 const AdjustData* p = static_cast<AdjustData*>(data);
409 GtkAllocation a;
410 gtk_widget_get_allocation(widget, &a);
411 a.x += p->dx;
412 a.y += p->dy;
413 gtk_widget_set_allocation(widget, &a);
414
415 if (gtk_widget_get_window(widget) == p->window)
416 {
417 // GtkFrame requires a queue_resize, otherwise parts of
418 // the frame newly exposed by the scroll are not drawn.
419 // To be safe, do it for all widgets.
420 gtk_widget_queue_resize_no_redraw(widget);
421 if (GTK_IS_CONTAINER(widget))
422 gtk_container_forall(GTK_CONTAINER(widget), scroll_adjust, data);
423 }
424 }
425 }
426
427 void wxPizza::scroll(int dx, int dy)
428 {
429 GtkWidget* widget = GTK_WIDGET(this);
430 if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL)
431 dx = -dx;
432 m_scroll_x -= dx;
433 m_scroll_y -= dy;
434 GdkWindow* window = gtk_widget_get_window(widget);
435 if (window)
436 {
437 gdk_window_scroll(window, dx, dy);
438 // Adjust child allocations. Doing a queue_resize on the children is not
439 // enough, sometimes they redraw in the wrong place during fast scrolling.
440 AdjustData data = { window, dx, dy };
441 gtk_container_forall(GTK_CONTAINER(widget), scroll_adjust, &data);
442 }
443 }
444
445 void wxPizza::get_border(GtkBorder& border)
446 {
447 #ifndef __WXUNIVERSAL__
448 if (m_windowStyle & wxBORDER_SIMPLE)
449 border.left = border.right = border.top = border.bottom = 1;
450 else if (m_windowStyle & (wxBORDER_RAISED | wxBORDER_SUNKEN | wxBORDER_THEME))
451 {
452 #ifdef __WXGTK3__
453 GtkStyleContext* sc;
454 if (m_windowStyle & (wxHSCROLL | wxVSCROLL))
455 sc = gtk_widget_get_style_context(wxGTKPrivate::GetTreeWidget());
456 else
457 sc = gtk_widget_get_style_context(wxGTKPrivate::GetEntryWidget());
458
459 gtk_style_context_get_border(sc, GTK_STATE_FLAG_NORMAL, &border);
460 #else // !__WXGTK3__
461 GtkStyle* style;
462 if (m_windowStyle & (wxHSCROLL | wxVSCROLL))
463 style = gtk_widget_get_style(wxGTKPrivate::GetTreeWidget());
464 else
465 style = gtk_widget_get_style(wxGTKPrivate::GetEntryWidget());
466
467 border.left = border.right = style->xthickness;
468 border.top = border.bottom = style->ythickness;
469 #endif // !__WXGTK3__
470 }
471 else
472 #endif // !__WXUNIVERSAL__
473 {
474 border.left = border.right = border.top = border.bottom = 0;
475 }
476 }