1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: src/gtk/win_gtk.cpp
3 // Purpose: native GTK+ widget for wxWindow
4 // Author: Paul Cornett
6 // Copyright: (c) 2007 Paul Cornett
7 // Licence: wxWindows licence
8 ///////////////////////////////////////////////////////////////////////////////
11 #include "wx/gtk/private.h"
12 #include "wx/gtk/private/win_gtk.h"
15 wxPizza is a custom GTK+ widget derived from GtkFixed. A custom widget
16 is needed to adapt GTK+ to wxWidgets needs in 3 areas: scrolling, window
19 For scrolling, the "set_scroll_adjustments" signal is implemented
20 to make wxPizza appear scrollable to GTK+, allowing it to be put in a
21 GtkScrolledWindow. Child widget positions are adjusted for the scrolling
22 position in size_allocate. A second same-size GdkWindow is placed behind
23 widget->window, to allow GDK to use a more efficient scrolling technique.
25 For borders, space is reserved in realize and size_allocate. The border is
26 drawn on wxPizza's parent GdkWindow.
28 For RTL, child widget positions are mirrored in size_allocate.
31 static GtkWidgetClass
* parent_class
;
38 void (*set_scroll_adjustments
)(GtkWidget
*, GtkAdjustment
*, GtkAdjustment
*);
41 static void size_allocate(GtkWidget
* widget
, GtkAllocation
* alloc
)
43 const bool is_resize
=
44 widget
->allocation
.width
!= alloc
->width
||
45 widget
->allocation
.height
!= alloc
->height
;
47 widget
->allocation
.x
!= alloc
->x
||
48 widget
->allocation
.y
!= alloc
->y
;
50 widget
->allocation
= *alloc
;
52 wxPizza
* pizza
= WX_PIZZA(widget
);
53 int border_x
, border_y
;
54 pizza
->get_border_widths(border_x
, border_y
);
55 int w
= alloc
->width
- 2 * border_x
;
58 if (GTK_WIDGET_REALIZED(widget
) && (is_move
|| is_resize
))
60 int h
= alloc
->height
- 2 * border_y
;
63 if (pizza
->m_is_scrollable
)
65 // two windows, both same size
66 gdk_window_move_resize(pizza
->m_backing_window
,
67 alloc
->x
+ border_x
, alloc
->y
+ border_y
, w
, h
);
69 gdk_window_resize(widget
->window
, w
, h
);
74 gdk_window_move_resize(widget
->window
,
75 alloc
->x
+ border_x
, alloc
->y
+ border_y
, w
, h
);
77 if ((border_x
> 0) || (border_y
> 0))
79 // Otherwise we get can redraw artefacts from
81 GtkWidget
*parent
= gtk_widget_get_parent( widget
);
82 gtk_widget_queue_draw( parent
);
86 // adjust child positions
87 for (const GList
* list
= pizza
->m_fixed
.children
; list
; list
= list
->next
)
89 const GtkFixedChild
* child
= static_cast<GtkFixedChild
*>(list
->data
);
90 if (GTK_WIDGET_VISIBLE(child
->widget
))
92 GtkAllocation child_alloc
;
93 // note that child positions do not take border into
94 // account, they need to be relative to widget->window,
95 // which has already been adjusted
96 child_alloc
.x
= child
->x
- pizza
->m_scroll_x
;
97 child_alloc
.y
= child
->y
- pizza
->m_scroll_y
;
99 gtk_widget_get_child_requisition(child
->widget
, &req
);
100 child_alloc
.width
= req
.width
;
101 child_alloc
.height
= req
.height
;
102 if (gtk_widget_get_direction(widget
) == GTK_TEXT_DIR_RTL
)
104 child_alloc
.x
= w
- child_alloc
.x
- child_alloc
.width
;
106 gtk_widget_size_allocate(child
->widget
, &child_alloc
);
111 static void realize(GtkWidget
* widget
)
113 parent_class
->realize(widget
);
115 wxPizza
* pizza
= WX_PIZZA(widget
);
116 if (pizza
->m_border_style
|| pizza
->m_is_scrollable
)
118 int border_x
, border_y
;
119 pizza
->get_border_widths(border_x
, border_y
);
120 int x
= widget
->allocation
.x
+ border_x
;
121 int y
= widget
->allocation
.y
+ border_y
;
122 int w
= widget
->allocation
.width
- 2 * border_x
;
123 int h
= widget
->allocation
.height
- 2 * border_y
;
126 if (pizza
->m_is_scrollable
)
128 // second window is created if wxWindow is scrollable
135 attr
.wclass
= GDK_INPUT_OUTPUT
;
136 attr
.visual
= gtk_widget_get_visual(widget
);
137 attr
.colormap
= gtk_widget_get_colormap(widget
);
138 attr
.window_type
= GDK_WINDOW_CHILD
;
140 pizza
->m_backing_window
= gdk_window_new(
141 gdk_window_get_parent(widget
->window
),
143 GDK_WA_X
| GDK_WA_Y
| GDK_WA_VISUAL
| GDK_WA_COLORMAP
);
145 gdk_window_set_user_data(pizza
->m_backing_window
, widget
);
146 gdk_window_reparent(widget
->window
, pizza
->m_backing_window
, 0, 0);
147 gdk_window_resize(widget
->window
, w
, h
);
149 // Parts of m_backing_window may be exposed temporarily while
150 // resizing. Setting the backing pixmap to None prevents those
151 // areas from being briefly painted black.
152 gdk_window_set_back_pixmap(pizza
->m_backing_window
, NULL
, false);
155 gdk_window_move_resize(widget
->window
, x
, y
, w
, h
);
159 static void unrealize(GtkWidget
* widget
)
161 parent_class
->unrealize(widget
);
163 wxPizza
* pizza
= WX_PIZZA(widget
);
164 if (pizza
->m_backing_window
)
166 gdk_window_set_user_data(pizza
->m_backing_window
, NULL
);
167 gdk_window_destroy(pizza
->m_backing_window
);
168 pizza
->m_backing_window
= NULL
;
172 static void map(GtkWidget
* widget
)
174 parent_class
->map(widget
);
176 wxPizza
* pizza
= WX_PIZZA(widget
);
177 if (pizza
->m_backing_window
)
178 gdk_window_show(pizza
->m_backing_window
);
181 static void unmap(GtkWidget
* widget
)
183 parent_class
->unmap(widget
);
185 wxPizza
* pizza
= WX_PIZZA(widget
);
186 if (pizza
->m_backing_window
)
187 gdk_window_hide(pizza
->m_backing_window
);
190 // not used, but needs to exist so gtk_widget_set_scroll_adjustments will work
191 static void set_scroll_adjustments(GtkWidget
*, GtkAdjustment
*, GtkAdjustment
*)
195 // Marshaller needed for set_scroll_adjustments signal,
196 // generated with GLib-2.4.6 glib-genmarshal
197 #define g_marshal_value_peek_object(v) g_value_get_object (v)
199 g_cclosure_user_marshal_VOID__OBJECT_OBJECT (GClosure
*closure
,
200 GValue
* /*return_value*/,
201 guint n_param_values
,
202 const GValue
*param_values
,
203 gpointer
/*invocation_hint*/,
204 gpointer marshal_data
)
206 typedef void (*GMarshalFunc_VOID__OBJECT_OBJECT
) (gpointer data1
,
210 register GMarshalFunc_VOID__OBJECT_OBJECT callback
;
211 register GCClosure
*cc
= (GCClosure
*) closure
;
212 register gpointer data1
, data2
;
214 g_return_if_fail (n_param_values
== 3);
216 if (G_CCLOSURE_SWAP_DATA (closure
))
218 data1
= closure
->data
;
219 data2
= g_value_peek_pointer (param_values
+ 0);
223 data1
= g_value_peek_pointer (param_values
+ 0);
224 data2
= closure
->data
;
226 callback
= (GMarshalFunc_VOID__OBJECT_OBJECT
) (marshal_data
? marshal_data
: cc
->callback
);
229 g_marshal_value_peek_object (param_values
+ 1),
230 g_marshal_value_peek_object (param_values
+ 2),
234 static void class_init(void* g_class
, void*)
236 GtkWidgetClass
* widget_class
= (GtkWidgetClass
*)g_class
;
237 widget_class
->size_allocate
= size_allocate
;
238 widget_class
->realize
= realize
;
239 widget_class
->unrealize
= unrealize
;
240 widget_class
->map
= map
;
241 widget_class
->unmap
= unmap
;
242 wxPizzaClass
* klass
= (wxPizzaClass
*)g_class
;
244 // needed to make widget appear scrollable to GTK+
245 klass
->set_scroll_adjustments
= set_scroll_adjustments
;
246 widget_class
->set_scroll_adjustments_signal
=
248 "set_scroll_adjustments",
249 G_TYPE_FROM_CLASS(g_class
),
251 G_STRUCT_OFFSET(wxPizzaClass
, set_scroll_adjustments
),
253 g_cclosure_user_marshal_VOID__OBJECT_OBJECT
,
254 G_TYPE_NONE
, 2, GTK_TYPE_ADJUSTMENT
, GTK_TYPE_ADJUSTMENT
);
256 parent_class
= GTK_WIDGET_CLASS(g_type_class_peek_parent(g_class
));
261 GType
wxPizza::type()
266 const GTypeInfo info
= {
267 sizeof(wxPizzaClass
),
274 type
= g_type_register_static(
275 GTK_TYPE_FIXED
, "wxPizza", &info
, GTypeFlags(0));
280 GtkWidget
* wxPizza::New(long windowStyle
)
282 GtkWidget
* widget
= GTK_WIDGET(g_object_new(type(), NULL
));
283 wxPizza
* pizza
= WX_PIZZA(widget
);
284 pizza
->m_backing_window
= NULL
;
285 pizza
->m_scroll_x
= 0;
286 pizza
->m_scroll_y
= 0;
287 pizza
->m_is_scrollable
= (windowStyle
& (wxHSCROLL
| wxVSCROLL
)) != 0;
288 // mask off border styles not useable with wxPizza
289 pizza
->m_border_style
= int(windowStyle
& BORDER_STYLES
);
290 gtk_fixed_set_has_window(GTK_FIXED(widget
), true);
291 gtk_widget_add_events(widget
,
294 GDK_POINTER_MOTION_MASK
|
295 GDK_POINTER_MOTION_HINT_MASK
|
296 GDK_BUTTON_MOTION_MASK
|
297 GDK_BUTTON1_MOTION_MASK
|
298 GDK_BUTTON2_MOTION_MASK
|
299 GDK_BUTTON3_MOTION_MASK
|
300 GDK_BUTTON_PRESS_MASK
|
301 GDK_BUTTON_RELEASE_MASK
|
303 GDK_KEY_RELEASE_MASK
|
304 GDK_ENTER_NOTIFY_MASK
|
305 GDK_LEAVE_NOTIFY_MASK
|
306 GDK_FOCUS_CHANGE_MASK
);
310 // gtk_fixed_move does not check for a change before issuing a queue_resize,
311 // we need to avoid that to prevent endless sizing loops, so check first
312 void wxPizza::move(GtkWidget
* widget
, int x
, int y
)
314 GtkFixed
* fixed
= &m_fixed
;
315 for (const GList
* list
= fixed
->children
; list
; list
= list
->next
)
317 const GtkFixedChild
* child
= static_cast<GtkFixedChild
*>(list
->data
);
318 if (child
->widget
== widget
)
320 if (child
->x
!= x
|| child
->y
!= y
)
321 gtk_fixed_move(fixed
, widget
, x
, y
);
332 // Adjust allocations for all widgets using the GdkWindow which was just scrolled
334 static void scroll_adjust(GtkWidget
* widget
, void* data
)
336 const AdjustData
* p
= static_cast<AdjustData
*>(data
);
337 if (widget
->window
== p
->window
)
339 widget
->allocation
.x
+= p
->dx
;
340 widget
->allocation
.y
+= p
->dy
;
341 // GtkFrame requires a queue_resize, otherwise parts of
342 // the frame newly exposed by the scroll are not drawn.
343 // To be safe, do it for all widgets.
344 gtk_widget_queue_resize_no_redraw(widget
);
345 if (GTK_IS_CONTAINER(widget
))
346 gtk_container_forall(GTK_CONTAINER(widget
), scroll_adjust
, data
);
351 void wxPizza::scroll(int dx
, int dy
)
353 GtkWidget
* widget
= GTK_WIDGET(this);
354 if (gtk_widget_get_direction(widget
) == GTK_TEXT_DIR_RTL
)
360 gdk_window_scroll(widget
->window
, dx
, dy
);
361 // Adjust child allocations. Doing a queue_resize on the children is not
362 // enough, sometimes they redraw in the wrong place during fast scrolling.
363 AdjustData data
= { widget
->window
, dx
, dy
};
364 gtk_container_forall(GTK_CONTAINER(widget
), scroll_adjust
, &data
);
368 void wxPizza::get_border_widths(int& x
, int& y
)
371 #ifndef __WXUNIVERSAL__
372 if (m_border_style
& wxBORDER_SIMPLE
)
374 else if (m_border_style
)
376 GtkWidget
*entry_widget
= wxGTKPrivate::GetEntryWidget();
377 if (entry_widget
->style
)
379 x
= entry_widget
->style
->xthickness
;
380 y
= entry_widget
->style
->ythickness
;