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
pizza_size_allocate(GtkWidget * widget,GtkAllocation * alloc)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
pizza_realize(GtkWidget * widget)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
pizza_show(GtkWidget * widget)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
pizza_hide(GtkWidget * widget)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
pizza_add(GtkContainer * container,GtkWidget * widget)172 static void pizza_add(GtkContainer* container, GtkWidget* widget)
173 {
174 WX_PIZZA(container)->put(widget, 0, 0, 1, 1);
175 }
176
pizza_remove(GtkContainer * container,GtkWidget * widget)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 // Get preferred size of children, to avoid GTK+ warnings complaining
196 // that they were size-allocated without asking their preferred size
children_get_preferred_size(const GList * p)197 static void children_get_preferred_size(const GList* p)
198 {
199 for (; p; p = p->next)
200 {
201 const wxPizzaChild* child = static_cast<wxPizzaChild*>(p->data);
202 if (gtk_widget_get_visible(child->widget))
203 {
204 GtkRequisition req;
205 gtk_widget_get_preferred_size(child->widget, &req, NULL);
206 }
207 }
208 }
209
pizza_get_preferred_width(GtkWidget * widget,int * minimum,int * natural)210 static void pizza_get_preferred_width(GtkWidget* widget, int* minimum, int* natural)
211 {
212 children_get_preferred_size(WX_PIZZA(widget)->m_children);
213 *minimum = 0;
214 gtk_widget_get_size_request(widget, natural, NULL);
215 if (*natural < 0)
216 *natural = 0;
217 }
218
pizza_get_preferred_height(GtkWidget * widget,int * minimum,int * natural)219 static void pizza_get_preferred_height(GtkWidget* widget, int* minimum, int* natural)
220 {
221 children_get_preferred_size(WX_PIZZA(widget)->m_children);
222 *minimum = 0;
223 gtk_widget_get_size_request(widget, NULL, natural);
224 if (*natural < 0)
225 *natural = 0;
226 }
227
pizza_adjust_size_request(GtkWidget * widget,GtkOrientation orientation,int * minimum,int * natural)228 static void pizza_adjust_size_request(GtkWidget* widget, GtkOrientation orientation, int* minimum, int* natural)
229 {
230 parent_class->adjust_size_request(widget, orientation, minimum, natural);
231 // Override adjustments to minimum size. GtkWidgetClass.adjust_size_request()
232 // will use the size request, if set, as the minimum.
233 // But don't override if in a GtkToolbar, it uses the minimum as actual size.
234 GtkWidget* parent = gtk_widget_get_parent(widget);
235 if (parent)
236 parent = gtk_widget_get_parent(parent);
237 if (!GTK_IS_TOOL_ITEM(parent))
238 *minimum = 0;
239 }
240
241 // Needed to implement GtkScrollable interface, but we don't care about the
242 // properties. wxWindowGTK handles the adjustments and scroll policy.
pizza_get_property(GObject *,guint,GValue *,GParamSpec *)243 static void pizza_get_property(GObject*, guint, GValue*, GParamSpec*)
244 {
245 }
246
pizza_set_property(GObject *,guint,const GValue *,GParamSpec *)247 static void pizza_set_property(GObject*, guint, const GValue*, GParamSpec*)
248 {
249 }
250 #else
251 // not used, but needs to exist so gtk_widget_set_scroll_adjustments will work
pizza_set_scroll_adjustments(GtkWidget *,GtkAdjustment *,GtkAdjustment *)252 static void pizza_set_scroll_adjustments(GtkWidget*, GtkAdjustment*, GtkAdjustment*)
253 {
254 }
255
256 // Marshaller needed for set_scroll_adjustments signal,
257 // generated with GLib-2.4.6 glib-genmarshal
258 #define g_marshal_value_peek_object(v) g_value_get_object (v)
259 static void
g_cclosure_user_marshal_VOID__OBJECT_OBJECT(GClosure * closure,GValue *,guint n_param_values,const GValue * param_values,gpointer,gpointer marshal_data)260 g_cclosure_user_marshal_VOID__OBJECT_OBJECT (GClosure *closure,
261 GValue * /*return_value*/,
262 guint n_param_values,
263 const GValue *param_values,
264 gpointer /*invocation_hint*/,
265 gpointer marshal_data)
266 {
267 typedef void (*GMarshalFunc_VOID__OBJECT_OBJECT) (gpointer data1,
268 gpointer arg_1,
269 gpointer arg_2,
270 gpointer data2);
271 GMarshalFunc_VOID__OBJECT_OBJECT callback;
272 GCClosure *cc = (GCClosure*) closure;
273 gpointer data1, data2;
274
275 g_return_if_fail (n_param_values == 3);
276
277 if (G_CCLOSURE_SWAP_DATA (closure))
278 {
279 data1 = closure->data;
280 data2 = g_value_peek_pointer (param_values + 0);
281 }
282 else
283 {
284 data1 = g_value_peek_pointer (param_values + 0);
285 data2 = closure->data;
286 }
287 callback = (GMarshalFunc_VOID__OBJECT_OBJECT) (marshal_data ? marshal_data : cc->callback);
288
289 callback (data1,
290 g_marshal_value_peek_object (param_values + 1),
291 g_marshal_value_peek_object (param_values + 2),
292 data2);
293 }
294 #endif
295
class_init(void * g_class,void *)296 static void class_init(void* g_class, void*)
297 {
298 GtkWidgetClass* widget_class = (GtkWidgetClass*)g_class;
299 widget_class->size_allocate = pizza_size_allocate;
300 widget_class->realize = pizza_realize;
301 widget_class->show = pizza_show;
302 widget_class->hide = pizza_hide;
303 GtkContainerClass* container_class = (GtkContainerClass*)g_class;
304 container_class->add = pizza_add;
305 container_class->remove = pizza_remove;
306
307 #ifdef __WXGTK3__
308 widget_class->get_preferred_width = pizza_get_preferred_width;
309 widget_class->get_preferred_height = pizza_get_preferred_height;
310 widget_class->adjust_size_request = pizza_adjust_size_request;
311 GObjectClass *gobject_class = G_OBJECT_CLASS(g_class);
312 gobject_class->set_property = pizza_set_property;
313 gobject_class->get_property = pizza_get_property;
314 g_object_class_override_property(gobject_class, PROP_HADJUSTMENT, "hadjustment");
315 g_object_class_override_property(gobject_class, PROP_VADJUSTMENT, "vadjustment");
316 g_object_class_override_property(gobject_class, PROP_HSCROLL_POLICY, "hscroll-policy");
317 g_object_class_override_property(gobject_class, PROP_VSCROLL_POLICY, "vscroll-policy");
318 #else
319 wxPizzaClass* klass = static_cast<wxPizzaClass*>(g_class);
320 // needed to make widget appear scrollable to GTK+
321 klass->set_scroll_adjustments = pizza_set_scroll_adjustments;
322 widget_class->set_scroll_adjustments_signal =
323 g_signal_new(
324 "set_scroll_adjustments",
325 G_TYPE_FROM_CLASS(g_class),
326 G_SIGNAL_RUN_LAST,
327 G_STRUCT_OFFSET(wxPizzaClass, set_scroll_adjustments),
328 NULL, NULL,
329 g_cclosure_user_marshal_VOID__OBJECT_OBJECT,
330 G_TYPE_NONE, 2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT);
331 #endif
332 parent_class = GTK_WIDGET_CLASS(g_type_class_peek_parent(g_class));
333 }
334
335 } // extern "C"
336
type()337 GType wxPizza::type()
338 {
339 static GType type;
340 if (type == 0)
341 {
342 const char* name = "wxPizza";
343 char buf[30];
344 for (unsigned i = 0; g_type_from_name(name); i++)
345 {
346 g_snprintf(buf, sizeof(buf), "wxPizza%u", i);
347 name = buf;
348 }
349 const GTypeInfo info = {
350 sizeof(wxPizzaClass),
351 NULL, NULL,
352 class_init,
353 NULL, NULL,
354 sizeof(wxPizza), 0,
355 NULL, NULL
356 };
357 type = g_type_register_static(
358 GTK_TYPE_FIXED, name, &info, GTypeFlags(0));
359 #ifdef __WXGTK3__
360 const GInterfaceInfo interface_info = { NULL, NULL, NULL };
361 g_type_add_interface_static(type, GTK_TYPE_SCROLLABLE, &interface_info);
362 #endif
363 }
364 return type;
365 }
366
New(long windowStyle)367 GtkWidget* wxPizza::New(long windowStyle)
368 {
369 GtkWidget* widget = GTK_WIDGET(g_object_new(type(), NULL));
370 wxPizza* pizza = WX_PIZZA(widget);
371 pizza->m_children = NULL;
372 pizza->m_scroll_x = 0;
373 pizza->m_scroll_y = 0;
374 pizza->m_windowStyle = windowStyle;
375 #ifdef __WXGTK3__
376 gtk_widget_set_has_window(widget, true);
377 #else
378 gtk_fixed_set_has_window(GTK_FIXED(widget), true);
379 #endif
380 gtk_widget_add_events(widget,
381 GDK_EXPOSURE_MASK |
382 GDK_SCROLL_MASK |
383 #if GTK_CHECK_VERSION(3,4,0)
384 GDK_SMOOTH_SCROLL_MASK |
385 #endif
386 GDK_POINTER_MOTION_MASK |
387 GDK_POINTER_MOTION_HINT_MASK |
388 GDK_BUTTON_MOTION_MASK |
389 GDK_BUTTON1_MOTION_MASK |
390 GDK_BUTTON2_MOTION_MASK |
391 GDK_BUTTON3_MOTION_MASK |
392 GDK_BUTTON_PRESS_MASK |
393 GDK_BUTTON_RELEASE_MASK |
394 GDK_KEY_PRESS_MASK |
395 GDK_KEY_RELEASE_MASK |
396 GDK_ENTER_NOTIFY_MASK |
397 GDK_LEAVE_NOTIFY_MASK |
398 GDK_FOCUS_CHANGE_MASK);
399 return widget;
400 }
401
move(GtkWidget * widget,int x,int y,int width,int height)402 void wxPizza::move(GtkWidget* widget, int x, int y, int width, int height)
403 {
404 for (const GList* p = m_children; p; p = p->next)
405 {
406 wxPizzaChild* child = static_cast<wxPizzaChild*>(p->data);
407 if (child->widget == widget)
408 {
409 child->x = x;
410 child->y = y;
411 child->width = width;
412 child->height = height;
413 // normally a queue-resize would be needed here, but we know
414 // wxWindowGTK::DoMoveWindow() will take care of it
415 break;
416 }
417 }
418 }
419
put(GtkWidget * widget,int x,int y,int width,int height)420 void wxPizza::put(GtkWidget* widget, int x, int y, int width, int height)
421 {
422 // Re-parenting a TLW under a child window is possible at wx level but
423 // using a TLW as child at GTK+ level results in problems, so don't do it.
424 if (!gtk_widget_is_toplevel(GTK_WIDGET(widget)))
425 gtk_fixed_put(GTK_FIXED(this), widget, 0, 0);
426
427 wxPizzaChild* child = new wxPizzaChild;
428 child->widget = widget;
429 child->x = x;
430 child->y = y;
431 child->width = width;
432 child->height = height;
433 m_children = g_list_append(m_children, child);
434 }
435
436 struct AdjustData {
437 GdkWindow* window;
438 int dx, dy;
439 };
440
441 // Adjust allocations for all widgets using the GdkWindow which was just scrolled
442 extern "C" {
scroll_adjust(GtkWidget * widget,void * data)443 static void scroll_adjust(GtkWidget* widget, void* data)
444 {
445 const AdjustData* p = static_cast<AdjustData*>(data);
446 GtkAllocation a;
447 gtk_widget_get_allocation(widget, &a);
448 a.x += p->dx;
449 a.y += p->dy;
450 gtk_widget_set_allocation(widget, &a);
451
452 if (gtk_widget_get_window(widget) == p->window)
453 {
454 // GtkFrame requires a queue_resize, otherwise parts of
455 // the frame newly exposed by the scroll are not drawn.
456 // To be safe, do it for all widgets.
457 gtk_widget_queue_resize_no_redraw(widget);
458 if (GTK_IS_CONTAINER(widget))
459 gtk_container_forall(GTK_CONTAINER(widget), scroll_adjust, data);
460 }
461 }
462 }
463
scroll(int dx,int dy)464 void wxPizza::scroll(int dx, int dy)
465 {
466 GtkWidget* widget = GTK_WIDGET(this);
467 if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL)
468 dx = -dx;
469 m_scroll_x -= dx;
470 m_scroll_y -= dy;
471 GdkWindow* window = gtk_widget_get_window(widget);
472 if (window)
473 {
474 gdk_window_scroll(window, dx, dy);
475 // Adjust child allocations. Doing a queue_resize on the children is not
476 // enough, sometimes they redraw in the wrong place during fast scrolling.
477 AdjustData data = { window, dx, dy };
478 gtk_container_forall(GTK_CONTAINER(widget), scroll_adjust, &data);
479 }
480 }
481
get_border(GtkBorder & border)482 void wxPizza::get_border(GtkBorder& border)
483 {
484 #ifndef __WXUNIVERSAL__
485 if (m_windowStyle & wxBORDER_SIMPLE)
486 border.left = border.right = border.top = border.bottom = 1;
487 else if (m_windowStyle & (wxBORDER_RAISED | wxBORDER_SUNKEN | wxBORDER_THEME))
488 {
489 #ifdef __WXGTK3__
490 GtkStyleContext* sc;
491 if (m_windowStyle & (wxHSCROLL | wxVSCROLL))
492 sc = gtk_widget_get_style_context(wxGTKPrivate::GetTreeWidget());
493 else
494 sc = gtk_widget_get_style_context(wxGTKPrivate::GetEntryWidget());
495
496 gtk_style_context_set_state(sc, GTK_STATE_FLAG_NORMAL);
497 gtk_style_context_get_border(sc, GTK_STATE_FLAG_NORMAL, &border);
498 #else // !__WXGTK3__
499 GtkStyle* style;
500 if (m_windowStyle & (wxHSCROLL | wxVSCROLL))
501 style = gtk_widget_get_style(wxGTKPrivate::GetTreeWidget());
502 else
503 style = gtk_widget_get_style(wxGTKPrivate::GetEntryWidget());
504
505 border.left = border.right = style->xthickness;
506 border.top = border.bottom = style->ythickness;
507 #endif // !__WXGTK3__
508 }
509 else
510 #endif // !__WXUNIVERSAL__
511 {
512 border.left = border.right = border.top = border.bottom = 0;
513 }
514 }
515