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