]> git.saurik.com Git - wxWidgets.git/blob - src/gtk/win_gtk.cpp
update size hints when decoration size becomes known, and preserve size hint increments
[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 #include "wx/gtk/private.h"
14 #include "wx/gtk/private/win_gtk.h"
15
16 /*
17 wxPizza is a custom GTK+ widget derived from GtkFixed. A custom widget
18 is needed to adapt GTK+ to wxWidgets needs in 3 areas: scrolling, window
19 borders, and RTL.
20
21 For scrolling, the "set_scroll_adjustments" signal is implemented
22 to make wxPizza appear scrollable to GTK+, allowing it to be put in a
23 GtkScrolledWindow. Child widget positions are adjusted for the scrolling
24 position in size_allocate.
25
26 For borders, space is reserved in realize and size_allocate. The border is
27 drawn on wxPizza's parent GdkWindow.
28
29 For RTL, child widget positions are mirrored in size_allocate.
30 */
31
32 struct wxPizzaChild
33 {
34 GtkWidget* widget;
35 int x, y, width, height;
36 };
37
38 static GtkWidgetClass* parent_class;
39
40 extern "C" {
41
42 struct wxPizzaClass
43 {
44 GtkFixedClass parent;
45 void (*set_scroll_adjustments)(GtkWidget*, GtkAdjustment*, GtkAdjustment*);
46 };
47
48 static void size_allocate(GtkWidget* widget, GtkAllocation* alloc)
49 {
50 wxPizza* pizza = WX_PIZZA(widget);
51 int border_x, border_y;
52 pizza->get_border_widths(border_x, border_y);
53 int w = alloc->width - 2 * border_x;
54 if (w < 0) w = 0;
55
56 if (gtk_widget_get_realized(widget))
57 {
58 int h = alloc->height - 2 * border_y;
59 if (h < 0) h = 0;
60 const int x = alloc->x + border_x;
61 const int y = alloc->y + border_y;
62
63 GdkWindow* window = gtk_widget_get_window(widget);
64 int old_x, old_y;
65 gdk_window_get_position(window, &old_x, &old_y);
66
67 if (x != old_x || y != old_y ||
68 w != gdk_window_get_width(window) || h != gdk_window_get_height(window))
69 {
70 gdk_window_move_resize(window, x, y, w, h);
71
72 if (border_x + border_y)
73 {
74 // old and new border areas need to be invalidated,
75 // otherwise they will not be erased/redrawn properly
76 GdkWindow* parent = gtk_widget_get_parent_window(widget);
77 gdk_window_invalidate_rect(parent, &widget->allocation, false);
78 gdk_window_invalidate_rect(parent, alloc, false);
79 }
80 }
81 }
82
83 widget->allocation = *alloc;
84
85 // adjust child positions
86 for (const GList* p = pizza->m_children; p; p = p->next)
87 {
88 const wxPizzaChild* child = static_cast<wxPizzaChild*>(p->data);
89 if (gtk_widget_get_visible(child->widget))
90 {
91 GtkAllocation child_alloc;
92 // note that child positions do not take border into
93 // account, they need to be relative to widget->window,
94 // which has already been adjusted
95 child_alloc.x = child->x - pizza->m_scroll_x;
96 child_alloc.y = child->y - pizza->m_scroll_y;
97 child_alloc.width = child->width;
98 child_alloc.height = child->height;
99 if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL)
100 child_alloc.x = w - child_alloc.x - child_alloc.width;
101 gtk_widget_size_allocate(child->widget, &child_alloc);
102 }
103 }
104 }
105
106 static void realize(GtkWidget* widget)
107 {
108 parent_class->realize(widget);
109
110 wxPizza* pizza = WX_PIZZA(widget);
111 if (pizza->m_border_style)
112 {
113 int border_x, border_y;
114 pizza->get_border_widths(border_x, border_y);
115 int x = widget->allocation.x + border_x;
116 int y = widget->allocation.y + border_y;
117 int w = widget->allocation.width - 2 * border_x;
118 int h = widget->allocation.height - 2 * border_y;
119 if (w < 0) w = 0;
120 if (h < 0) h = 0;
121 gdk_window_move_resize(widget->window, x, y, w, h);
122 }
123 }
124
125 static void show(GtkWidget* widget)
126 {
127 if (widget->parent && WX_PIZZA(widget)->m_border_style)
128 {
129 // invalidate whole allocation so borders will be drawn properly
130 const GtkAllocation& a = widget->allocation;
131 gtk_widget_queue_draw_area(widget->parent, a.x, a.y, a.width, a.height);
132 }
133
134 parent_class->show(widget);
135 }
136
137 static void hide(GtkWidget* widget)
138 {
139 if (widget->parent && WX_PIZZA(widget)->m_border_style)
140 {
141 // invalidate whole allocation so borders will be erased properly
142 const GtkAllocation& a = widget->allocation;
143 gtk_widget_queue_draw_area(widget->parent, a.x, a.y, a.width, a.height);
144 }
145
146 parent_class->hide(widget);
147 }
148
149 static void pizza_add(GtkContainer* container, GtkWidget* widget)
150 {
151 WX_PIZZA(container)->put(widget, 0, 0, 1, 1);
152 }
153
154 static void pizza_remove(GtkContainer* container, GtkWidget* widget)
155 {
156 GTK_CONTAINER_CLASS(parent_class)->remove(container, widget);
157
158 wxPizza* pizza = WX_PIZZA(container);
159 for (GList* p = pizza->m_children; p; p = p->next)
160 {
161 wxPizzaChild* child = static_cast<wxPizzaChild*>(p->data);
162 if (child->widget == widget)
163 {
164 pizza->m_children = g_list_delete_link(pizza->m_children, p);
165 delete child;
166 break;
167 }
168 }
169 }
170
171 // not used, but needs to exist so gtk_widget_set_scroll_adjustments will work
172 static void set_scroll_adjustments(GtkWidget*, GtkAdjustment*, GtkAdjustment*)
173 {
174 }
175
176 // Marshaller needed for set_scroll_adjustments signal,
177 // generated with GLib-2.4.6 glib-genmarshal
178 #define g_marshal_value_peek_object(v) g_value_get_object (v)
179 static void
180 g_cclosure_user_marshal_VOID__OBJECT_OBJECT (GClosure *closure,
181 GValue * /*return_value*/,
182 guint n_param_values,
183 const GValue *param_values,
184 gpointer /*invocation_hint*/,
185 gpointer marshal_data)
186 {
187 typedef void (*GMarshalFunc_VOID__OBJECT_OBJECT) (gpointer data1,
188 gpointer arg_1,
189 gpointer arg_2,
190 gpointer data2);
191 register GMarshalFunc_VOID__OBJECT_OBJECT callback;
192 register GCClosure *cc = (GCClosure*) closure;
193 register gpointer data1, data2;
194
195 g_return_if_fail (n_param_values == 3);
196
197 if (G_CCLOSURE_SWAP_DATA (closure))
198 {
199 data1 = closure->data;
200 data2 = g_value_peek_pointer (param_values + 0);
201 }
202 else
203 {
204 data1 = g_value_peek_pointer (param_values + 0);
205 data2 = closure->data;
206 }
207 callback = (GMarshalFunc_VOID__OBJECT_OBJECT) (marshal_data ? marshal_data : cc->callback);
208
209 callback (data1,
210 g_marshal_value_peek_object (param_values + 1),
211 g_marshal_value_peek_object (param_values + 2),
212 data2);
213 }
214
215 static void class_init(void* g_class, void*)
216 {
217 GtkWidgetClass* widget_class = (GtkWidgetClass*)g_class;
218 widget_class->size_allocate = size_allocate;
219 widget_class->realize = realize;
220 widget_class->show = show;
221 widget_class->hide = hide;
222 GtkContainerClass* container_class = (GtkContainerClass*)g_class;
223 container_class->add = pizza_add;
224 container_class->remove = pizza_remove;
225 wxPizzaClass* klass = (wxPizzaClass*)g_class;
226
227 // needed to make widget appear scrollable to GTK+
228 klass->set_scroll_adjustments = set_scroll_adjustments;
229 widget_class->set_scroll_adjustments_signal =
230 g_signal_new(
231 "set_scroll_adjustments",
232 G_TYPE_FROM_CLASS(g_class),
233 G_SIGNAL_RUN_LAST,
234 G_STRUCT_OFFSET(wxPizzaClass, set_scroll_adjustments),
235 NULL, NULL,
236 g_cclosure_user_marshal_VOID__OBJECT_OBJECT,
237 G_TYPE_NONE, 2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT);
238
239 parent_class = GTK_WIDGET_CLASS(g_type_class_peek_parent(g_class));
240 }
241
242 } // extern "C"
243
244 GType wxPizza::type()
245 {
246 static GtkType type;
247 if (type == 0)
248 {
249 const GTypeInfo info = {
250 sizeof(wxPizzaClass),
251 NULL, NULL,
252 class_init,
253 NULL, NULL,
254 sizeof(wxPizza), 0,
255 NULL, NULL
256 };
257 type = g_type_register_static(
258 GTK_TYPE_FIXED, "wxPizza", &info, GTypeFlags(0));
259 }
260 return type;
261 }
262
263 GtkWidget* wxPizza::New(long windowStyle)
264 {
265 GtkWidget* widget = GTK_WIDGET(g_object_new(type(), NULL));
266 wxPizza* pizza = WX_PIZZA(widget);
267 pizza->m_children = NULL;
268 pizza->m_scroll_x = 0;
269 pizza->m_scroll_y = 0;
270 pizza->m_is_scrollable = (windowStyle & (wxHSCROLL | wxVSCROLL)) != 0;
271 // mask off border styles not useable with wxPizza
272 pizza->m_border_style = int(windowStyle & BORDER_STYLES);
273 #if GTK_CHECK_VERSION(3,0,0) || defined(GTK_DISABLE_DEPRECATED)
274 gtk_widget_set_has_window(widget, true);
275 #else
276 gtk_fixed_set_has_window(GTK_FIXED(widget), true);
277 #endif
278 gtk_widget_add_events(widget,
279 GDK_EXPOSURE_MASK |
280 GDK_SCROLL_MASK |
281 GDK_POINTER_MOTION_MASK |
282 GDK_POINTER_MOTION_HINT_MASK |
283 GDK_BUTTON_MOTION_MASK |
284 GDK_BUTTON1_MOTION_MASK |
285 GDK_BUTTON2_MOTION_MASK |
286 GDK_BUTTON3_MOTION_MASK |
287 GDK_BUTTON_PRESS_MASK |
288 GDK_BUTTON_RELEASE_MASK |
289 GDK_KEY_PRESS_MASK |
290 GDK_KEY_RELEASE_MASK |
291 GDK_ENTER_NOTIFY_MASK |
292 GDK_LEAVE_NOTIFY_MASK |
293 GDK_FOCUS_CHANGE_MASK);
294 return widget;
295 }
296
297 void wxPizza::move(GtkWidget* widget, int x, int y, int width, int height)
298 {
299 for (const GList* p = m_children; p; p = p->next)
300 {
301 wxPizzaChild* child = static_cast<wxPizzaChild*>(p->data);
302 if (child->widget == widget)
303 {
304 child->x = x;
305 child->y = y;
306 child->width = width;
307 child->height = height;
308 // normally a queue-resize would be needed here, but we know
309 // wxWindowGTK::DoMoveWindow() will take care of it
310 break;
311 }
312 }
313 }
314
315 void wxPizza::put(GtkWidget* widget, int x, int y, int width, int height)
316 {
317 gtk_fixed_put(GTK_FIXED(this), widget, 0, 0);
318
319 wxPizzaChild* child = new wxPizzaChild;
320 child->widget = widget;
321 child->x = x;
322 child->y = y;
323 child->width = width;
324 child->height = height;
325 m_children = g_list_append(m_children, child);
326 }
327
328 struct AdjustData {
329 GdkWindow* window;
330 int dx, dy;
331 };
332
333 // Adjust allocations for all widgets using the GdkWindow which was just scrolled
334 extern "C" {
335 static void scroll_adjust(GtkWidget* widget, void* data)
336 {
337 const AdjustData* p = static_cast<AdjustData*>(data);
338 widget->allocation.x += p->dx;
339 widget->allocation.y += p->dy;
340
341 if (widget->window == p->window)
342 {
343 // GtkFrame requires a queue_resize, otherwise parts of
344 // the frame newly exposed by the scroll are not drawn.
345 // To be safe, do it for all widgets.
346 gtk_widget_queue_resize_no_redraw(widget);
347 if (GTK_IS_CONTAINER(widget))
348 gtk_container_forall(GTK_CONTAINER(widget), scroll_adjust, data);
349 }
350 }
351 }
352
353 void wxPizza::scroll(int dx, int dy)
354 {
355 GtkWidget* widget = GTK_WIDGET(this);
356 if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL)
357 dx = -dx;
358 m_scroll_x -= dx;
359 m_scroll_y -= dy;
360 if (widget->window)
361 {
362 gdk_window_scroll(widget->window, dx, dy);
363 // Adjust child allocations. Doing a queue_resize on the children is not
364 // enough, sometimes they redraw in the wrong place during fast scrolling.
365 AdjustData data = { widget->window, dx, dy };
366 gtk_container_forall(GTK_CONTAINER(widget), scroll_adjust, &data);
367 }
368 }
369
370 void wxPizza::get_border_widths(int& x, int& y)
371 {
372 x = y = 0;
373 if (m_border_style == 0)
374 return;
375
376 #ifndef __WXUNIVERSAL__
377 if (m_border_style & wxBORDER_SIMPLE)
378 x = y = 1;
379 else if (m_is_scrollable /* || (m_border_style & wxBORDER_THEME) */)
380 {
381 GtkWidget *style_widget = wxGTKPrivate::GetTreeWidget();
382
383 if (style_widget->style)
384 {
385 x = style_widget->style->xthickness;
386 y = style_widget->style->ythickness;
387 }
388 }
389 else
390 {
391 GtkWidget *style_widget = wxGTKPrivate::GetEntryWidget();
392
393 if (style_widget->style)
394 {
395 x = style_widget->style->xthickness;
396 y = style_widget->style->ythickness;
397 }
398 }
399 #endif
400 }