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/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.
26 For non-scrolling wxWindows, a second GdkWindow is used for the
27 border; scrolling wxWindows draw their border on wxPizza's parent
28 (a GtkScrolledWindow) GdkWindow. Border widths for sunken and raised
29 styles are taken from widget->style, so they will match the current theme.
31 For RTL, child widget positions are mirrored in size_allocate.
34 static GtkWidgetClass
* parent_class
;
41 void (*set_scroll_adjustments
)(GtkWidget
*, GtkAdjustment
*, GtkAdjustment
*);
44 static void size_allocate(GtkWidget
* widget
, GtkAllocation
* alloc
)
46 const bool is_resize
=
47 widget
->allocation
.width
!= alloc
->width
||
48 widget
->allocation
.height
!= alloc
->height
;
50 widget
->allocation
.x
!= alloc
->x
||
51 widget
->allocation
.y
!= alloc
->y
;
53 widget
->allocation
= *alloc
;
55 wxPizza
* pizza
= WX_PIZZA(widget
);
56 int border_x
, border_y
;
57 pizza
->get_border_widths(border_x
, border_y
);
58 int w
= alloc
->width
- 2 * border_x
;
61 if (GTK_WIDGET_REALIZED(widget
) && (is_move
|| is_resize
))
63 int h
= alloc
->height
- 2 * border_y
;
66 if (pizza
->m_is_scrollable
)
68 // backing window is inside border
69 gdk_window_move_resize(pizza
->m_backing_window
,
70 alloc
->x
+ border_x
, alloc
->y
+ border_y
, w
, h
);
74 // border window (or only window if
75 // no-scroll no-border) is full size
76 GdkWindow
* window
= pizza
->m_backing_window
;
78 window
= widget
->window
;
79 gdk_window_move_resize(
80 window
, alloc
->x
, alloc
->y
, alloc
->width
, alloc
->height
);
82 if (is_resize
&& pizza
->m_backing_window
)
84 // main window is inside border
85 if (pizza
->m_is_scrollable
)
86 gdk_window_resize(widget
->window
, w
, h
);
88 // need move as well as resize because border can change
89 gdk_window_move_resize(widget
->window
, border_x
, border_y
, w
, h
);
91 // wxWidgets turns off redraw-on-allocate by default,
92 // so border area needs to be invalidated
93 if (border_x
> 1 || border_y
> 1)
95 if (pizza
->m_is_scrollable
)
96 ; // invalidate does not seem to be needed in this case
98 gdk_window_invalidate_rect(pizza
->m_backing_window
, NULL
, false);
102 // adjust child positions
103 for (const GList
* list
= pizza
->m_fixed
.children
; list
; list
= list
->next
)
105 const GtkFixedChild
* child
= static_cast<GtkFixedChild
*>(list
->data
);
106 if (GTK_WIDGET_VISIBLE(child
->widget
))
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
;
115 gtk_widget_get_child_requisition(child
->widget
, &req
);
116 child_alloc
.width
= req
.width
;
117 child_alloc
.height
= req
.height
;
118 if (gtk_widget_get_direction(widget
) == GTK_TEXT_DIR_RTL
)
120 child_alloc
.x
= w
- child_alloc
.x
- child_alloc
.width
;
122 gtk_widget_size_allocate(child
->widget
, &child_alloc
);
127 static void realize(GtkWidget
* widget
)
129 parent_class
->realize(widget
);
131 wxPizza
* pizza
= WX_PIZZA(widget
);
132 // second window is created if there is a border, or wxWindow is scrollable
133 if (pizza
->m_border_style
|| pizza
->m_is_scrollable
)
136 attr
.event_mask
= pizza
->m_is_scrollable
? 0 : GDK_EXPOSURE_MASK
;
137 attr
.x
= widget
->allocation
.x
;
138 attr
.y
= widget
->allocation
.y
;
139 attr
.width
= widget
->allocation
.width
;
140 attr
.height
= widget
->allocation
.height
;
141 int border_x
, border_y
;
142 pizza
->get_border_widths(border_x
, border_y
);
143 int w
= widget
->allocation
.width
- 2 * border_x
;
144 int h
= widget
->allocation
.height
- 2 * border_y
;
147 if (pizza
->m_is_scrollable
)
154 attr
.wclass
= GDK_INPUT_OUTPUT
;
155 attr
.visual
= gtk_widget_get_visual(widget
);
156 attr
.colormap
= gtk_widget_get_colormap(widget
);
157 attr
.window_type
= GDK_WINDOW_CHILD
;
159 pizza
->m_backing_window
= gdk_window_new(
160 gdk_window_get_parent(widget
->window
),
162 GDK_WA_X
| GDK_WA_Y
| GDK_WA_VISUAL
| GDK_WA_COLORMAP
);
164 gdk_window_set_user_data(pizza
->m_backing_window
, widget
);
165 if (pizza
->m_is_scrollable
)
166 gdk_window_reparent(widget
->window
, pizza
->m_backing_window
, 0, 0);
168 gdk_window_reparent(widget
->window
, pizza
->m_backing_window
, border_x
, border_y
);
169 gdk_window_resize(widget
->window
, w
, h
);
171 widget
->style
= gtk_style_attach (widget
->style
, pizza
->m_backing_window
);
172 gtk_style_set_background (widget
->style
, pizza
->m_backing_window
, GTK_STATE_NORMAL
);
176 static void unrealize(GtkWidget
* widget
)
178 parent_class
->unrealize(widget
);
180 wxPizza
* pizza
= WX_PIZZA(widget
);
181 if (pizza
->m_backing_window
)
183 gdk_window_set_user_data(pizza
->m_backing_window
, NULL
);
184 gdk_window_destroy(pizza
->m_backing_window
);
185 pizza
->m_backing_window
= NULL
;
189 static void map(GtkWidget
* widget
)
191 parent_class
->map(widget
);
193 wxPizza
* pizza
= WX_PIZZA(widget
);
194 if (pizza
->m_backing_window
)
195 gdk_window_show(pizza
->m_backing_window
);
198 static void unmap(GtkWidget
* widget
)
200 parent_class
->unmap(widget
);
202 wxPizza
* pizza
= WX_PIZZA(widget
);
203 if (pizza
->m_backing_window
)
204 gdk_window_hide(pizza
->m_backing_window
);
207 // not used, but needs to exist so gtk_widget_set_scroll_adjustments will work
208 static void set_scroll_adjustments(GtkWidget
*, GtkAdjustment
*, GtkAdjustment
*)
212 // Marshaller needed for set_scroll_adjustments signal,
213 // generated with GLib-2.4.6 glib-genmarshal
214 #define g_marshal_value_peek_object(v) g_value_get_object (v)
216 g_cclosure_user_marshal_VOID__OBJECT_OBJECT (GClosure
*closure
,
217 GValue
* /*return_value*/,
218 guint n_param_values
,
219 const GValue
*param_values
,
220 gpointer
/*invocation_hint*/,
221 gpointer marshal_data
)
223 typedef void (*GMarshalFunc_VOID__OBJECT_OBJECT
) (gpointer data1
,
227 register GMarshalFunc_VOID__OBJECT_OBJECT callback
;
228 register GCClosure
*cc
= (GCClosure
*) closure
;
229 register gpointer data1
, data2
;
231 g_return_if_fail (n_param_values
== 3);
233 if (G_CCLOSURE_SWAP_DATA (closure
))
235 data1
= closure
->data
;
236 data2
= g_value_peek_pointer (param_values
+ 0);
240 data1
= g_value_peek_pointer (param_values
+ 0);
241 data2
= closure
->data
;
243 callback
= (GMarshalFunc_VOID__OBJECT_OBJECT
) (marshal_data
? marshal_data
: cc
->callback
);
246 g_marshal_value_peek_object (param_values
+ 1),
247 g_marshal_value_peek_object (param_values
+ 2),
251 static void class_init(void* g_class
, void*)
253 GtkWidgetClass
* widget_class
= (GtkWidgetClass
*)g_class
;
254 widget_class
->size_allocate
= size_allocate
;
255 widget_class
->realize
= realize
;
256 widget_class
->unrealize
= unrealize
;
257 widget_class
->map
= map
;
258 widget_class
->unmap
= unmap
;
259 wxPizzaClass
* klass
= (wxPizzaClass
*)g_class
;
261 // needed to make widget appear scrollable to GTK+
262 klass
->set_scroll_adjustments
= set_scroll_adjustments
;
263 widget_class
->set_scroll_adjustments_signal
=
265 "set_scroll_adjustments",
266 G_TYPE_FROM_CLASS(g_class
),
268 G_STRUCT_OFFSET(wxPizzaClass
, set_scroll_adjustments
),
270 g_cclosure_user_marshal_VOID__OBJECT_OBJECT
,
271 G_TYPE_NONE
, 2, GTK_TYPE_ADJUSTMENT
, GTK_TYPE_ADJUSTMENT
);
273 parent_class
= GTK_WIDGET_CLASS(g_type_class_peek_parent(g_class
));
278 GType
wxPizza::type()
283 const GTypeInfo info
= {
284 sizeof(wxPizzaClass
),
291 type
= g_type_register_static(
292 GTK_TYPE_FIXED
, "wxPizza", &info
, GTypeFlags(0));
297 GtkWidget
* wxPizza::New(long windowStyle
)
299 GtkWidget
* widget
= GTK_WIDGET(g_object_new(type(), NULL
));
300 wxPizza
* pizza
= WX_PIZZA(widget
);
301 pizza
->m_backing_window
= NULL
;
302 pizza
->m_scroll_x
= 0;
303 pizza
->m_scroll_y
= 0;
304 pizza
->m_is_scrollable
= (windowStyle
& (wxHSCROLL
| wxVSCROLL
)) != 0;
305 pizza
->m_border_style
=
306 int(windowStyle
& (wxBORDER_SIMPLE
| wxBORDER_RAISED
| wxBORDER_SUNKEN
));
307 gtk_fixed_set_has_window(GTK_FIXED(widget
), true);
308 gtk_widget_add_events(widget
,
311 GDK_POINTER_MOTION_MASK
|
312 GDK_POINTER_MOTION_HINT_MASK
|
313 GDK_BUTTON_MOTION_MASK
|
314 GDK_BUTTON1_MOTION_MASK
|
315 GDK_BUTTON2_MOTION_MASK
|
316 GDK_BUTTON3_MOTION_MASK
|
317 GDK_BUTTON_PRESS_MASK
|
318 GDK_BUTTON_RELEASE_MASK
|
320 GDK_KEY_RELEASE_MASK
|
321 GDK_ENTER_NOTIFY_MASK
|
322 GDK_LEAVE_NOTIFY_MASK
|
323 GDK_FOCUS_CHANGE_MASK
);
324 gtk_container_set_resize_mode(GTK_CONTAINER(widget
), GTK_RESIZE_QUEUE
);
328 // gtk_fixed_move does not check for a change before issuing a queue_resize,
329 // we need to avoid that to prevent endless sizing loops, so check first
330 void wxPizza::move(GtkWidget
* widget
, int x
, int y
)
332 GtkFixed
* fixed
= &m_fixed
;
333 for (const GList
* list
= fixed
->children
; list
; list
= list
->next
)
335 const GtkFixedChild
* child
= static_cast<GtkFixedChild
*>(list
->data
);
336 if (child
->widget
== widget
)
338 if (child
->x
!= x
|| child
->y
!= y
)
339 gtk_fixed_move(fixed
, widget
, x
, y
);
345 void wxPizza::scroll(int dx
, int dy
)
347 GtkWidget
* widget
= GTK_WIDGET(this);
348 if (gtk_widget_get_direction(widget
) == GTK_TEXT_DIR_RTL
)
353 gdk_window_scroll(widget
->window
, dx
, dy
);
354 const GList
* list
= m_fixed
.children
;
357 const GtkFixedChild
* child
= static_cast<GtkFixedChild
*>(list
->data
);
358 // queueing a resize on any child will update them all
359 gtk_widget_queue_resize(child
->widget
);
363 extern GtkWidget
*GetEntryWidget();
365 void wxPizza::get_border_widths(int& x
, int& y
)
368 if (m_border_style
& wxBORDER_SIMPLE
)
370 else if (m_border_style
)
372 GtkWidget
*entry_widget
= GetEntryWidget();
373 if (entry_widget
->style
)
375 x
= entry_widget
->style
->xthickness
;
376 y
= entry_widget
->style
->ythickness
;