]> git.saurik.com Git - wxWidgets.git/blame - src/gtk/win_gtk.cpp
missing commit
[wxWidgets.git] / src / gtk / win_gtk.cpp
CommitLineData
e56307d3
PC
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
e762ef8f
VZ
10#include "wx/wxprec.h"
11
e56307d3 12#include "wx/defs.h"
9dc44eff
PC
13
14#include <gtk/gtk.h>
bbd92d1d 15#include "wx/gtk/private/win_gtk.h"
e56307d3 16
9dc44eff
PC
17#include "wx/gtk/private.h"
18#include "wx/gtk/private/gtk2-compat.h"
19
e56307d3
PC
20/*
21wxPizza is a custom GTK+ widget derived from GtkFixed. A custom widget
22is needed to adapt GTK+ to wxWidgets needs in 3 areas: scrolling, window
23borders, and RTL.
24
25For scrolling, the "set_scroll_adjustments" signal is implemented
26to make wxPizza appear scrollable to GTK+, allowing it to be put in a
27GtkScrolledWindow. Child widget positions are adjusted for the scrolling
375efc1f 28position in size_allocate.
e56307d3 29
75f661bb
PC
30For borders, space is reserved in realize and size_allocate. The border is
31drawn on wxPizza's parent GdkWindow.
e56307d3
PC
32
33For RTL, child widget positions are mirrored in size_allocate.
34*/
35
3b7067a0
PC
36struct wxPizzaChild
37{
38 GtkWidget* widget;
39 int x, y, width, height;
40};
41
e56307d3
PC
42static GtkWidgetClass* parent_class;
43
9dc44eff
PC
44#ifdef __WXGTK3__
45enum {
46 PROP_0,
47 PROP_HADJUSTMENT,
48 PROP_VADJUSTMENT,
49 PROP_HSCROLL_POLICY,
50 PROP_VSCROLL_POLICY
51};
52#endif
53
e56307d3
PC
54extern "C" {
55
56struct wxPizzaClass
57{
58 GtkFixedClass parent;
9dc44eff 59#ifndef __WXGTK3__
e56307d3 60 void (*set_scroll_adjustments)(GtkWidget*, GtkAdjustment*, GtkAdjustment*);
9dc44eff 61#endif
e56307d3
PC
62};
63
9dc44eff 64static void pizza_size_allocate(GtkWidget* widget, GtkAllocation* alloc)
e56307d3 65{
e56307d3 66 wxPizza* pizza = WX_PIZZA(widget);
9dc44eff
PC
67 GtkBorder border;
68 pizza->get_border(border);
69 int w = alloc->width - border.left - border.right;
e56307d3
PC
70 if (w < 0) w = 0;
71
030f4112 72 if (gtk_widget_get_realized(widget))
e56307d3 73 {
9dc44eff 74 int h = alloc->height - border.top - border.bottom;
e56307d3 75 if (h < 0) h = 0;
9dc44eff
PC
76 const int x = alloc->x + border.left;
77 const int y = alloc->y + border.top;
e56307d3 78
030f4112
PC
79 GdkWindow* window = gtk_widget_get_window(widget);
80 int old_x, old_y;
81 gdk_window_get_position(window, &old_x, &old_y);
375efc1f 82
030f4112
PC
83 if (x != old_x || y != old_y ||
84 w != gdk_window_get_width(window) || h != gdk_window_get_height(window))
e56307d3 85 {
030f4112
PC
86 gdk_window_move_resize(window, x, y, w, h);
87
9dc44eff 88 if (border.left + border.right + border.top + border.bottom)
030f4112
PC
89 {
90 // old and new border areas need to be invalidated,
91 // otherwise they will not be erased/redrawn properly
9dc44eff
PC
92 GtkAllocation old_alloc;
93 gtk_widget_get_allocation(widget, &old_alloc);
030f4112 94 GdkWindow* parent = gtk_widget_get_parent_window(widget);
9dc44eff 95 gdk_window_invalidate_rect(parent, &old_alloc, false);
030f4112
PC
96 gdk_window_invalidate_rect(parent, alloc, false);
97 }
e56307d3
PC
98 }
99 }
7456fe19 100
9dc44eff 101 gtk_widget_set_allocation(widget, alloc);
03647350 102
e56307d3 103 // adjust child positions
3b7067a0 104 for (const GList* p = pizza->m_children; p; p = p->next)
e56307d3 105 {
3b7067a0 106 const wxPizzaChild* child = static_cast<wxPizzaChild*>(p->data);
fc9ab22a 107 if (gtk_widget_get_visible(child->widget))
e56307d3
PC
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;
3b7067a0
PC
115 child_alloc.width = child->width;
116 child_alloc.height = child->height;
e56307d3 117 if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL)
e56307d3 118 child_alloc.x = w - child_alloc.x - child_alloc.width;
18319143 119 gtk_widget_size_allocate(child->widget, &child_alloc);
e56307d3
PC
120 }
121 }
122}
123
9dc44eff 124static void pizza_realize(GtkWidget* widget)
e56307d3
PC
125{
126 parent_class->realize(widget);
127
128 wxPizza* pizza = WX_PIZZA(widget);
9dc44eff 129 if (pizza->m_windowStyle & wxPizza::BORDER_STYLES)
e56307d3 130 {
9dc44eff
PC
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;
e56307d3
PC
139 if (w < 0) w = 0;
140 if (h < 0) h = 0;
9dc44eff 141 gdk_window_move_resize(gtk_widget_get_window(widget), x, y, w, h);
e56307d3 142 }
e56307d3
PC
143}
144
9dc44eff 145static void pizza_show(GtkWidget* widget)
6e7cf3bd 146{
9dc44eff
PC
147 GtkWidget* parent = gtk_widget_get_parent(widget);
148 if (parent && (WX_PIZZA(widget)->m_windowStyle & wxPizza::BORDER_STYLES))
6e7cf3bd
PC
149 {
150 // invalidate whole allocation so borders will be drawn properly
9dc44eff
PC
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);
6e7cf3bd
PC
154 }
155
156 parent_class->show(widget);
157}
158
9dc44eff 159static void pizza_hide(GtkWidget* widget)
6e7cf3bd 160{
9dc44eff
PC
161 GtkWidget* parent = gtk_widget_get_parent(widget);
162 if (parent && (WX_PIZZA(widget)->m_windowStyle & wxPizza::BORDER_STYLES))
6e7cf3bd
PC
163 {
164 // invalidate whole allocation so borders will be erased properly
9dc44eff
PC
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);
6e7cf3bd
PC
168 }
169
170 parent_class->hide(widget);
171}
172
3b7067a0
PC
173static void pizza_add(GtkContainer* container, GtkWidget* widget)
174{
175 WX_PIZZA(container)->put(widget, 0, 0, 1, 1);
176}
177
178static 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
9dc44eff
PC
195#ifdef __WXGTK3__
196static 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
204static 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.
214static void pizza_get_property(GObject*, guint, GValue*, GParamSpec*)
215{
216}
217
218static void pizza_set_property(GObject*, guint, const GValue*, GParamSpec*)
219{
220}
221#else
e56307d3 222// not used, but needs to exist so gtk_widget_set_scroll_adjustments will work
9dc44eff 223static void pizza_set_scroll_adjustments(GtkWidget*, GtkAdjustment*, GtkAdjustment*)
e56307d3
PC
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)
230static void
231g_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}
9dc44eff 265#endif
e56307d3
PC
266
267static void class_init(void* g_class, void*)
268{
269 GtkWidgetClass* widget_class = (GtkWidgetClass*)g_class;
9dc44eff
PC
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;
3b7067a0
PC
274 GtkContainerClass* container_class = (GtkContainerClass*)g_class;
275 container_class->add = pizza_add;
276 container_class->remove = pizza_remove;
e56307d3 277
9dc44eff
PC
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);
e56307d3 290 // needed to make widget appear scrollable to GTK+
9dc44eff 291 klass->set_scroll_adjustments = pizza_set_scroll_adjustments;
e56307d3
PC
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);
9dc44eff 301#endif
e56307d3
PC
302 parent_class = GTK_WIDGET_CLASS(g_type_class_peek_parent(g_class));
303}
304
305} // extern "C"
306
307GType wxPizza::type()
308{
9dc44eff 309 static GType type;
e56307d3
PC
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));
9dc44eff
PC
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
e56307d3
PC
326 }
327 return type;
328}
329
0f52f610 330GtkWidget* wxPizza::New(long windowStyle)
e56307d3
PC
331{
332 GtkWidget* widget = GTK_WIDGET(g_object_new(type(), NULL));
333 wxPizza* pizza = WX_PIZZA(widget);
3b7067a0 334 pizza->m_children = NULL;
e56307d3
PC
335 pizza->m_scroll_x = 0;
336 pizza->m_scroll_y = 0;
9dc44eff
PC
337 pizza->m_windowStyle = windowStyle;
338#ifdef __WXGTK3__
fc9ab22a
VS
339 gtk_widget_set_has_window(widget, true);
340#else
e56307d3 341 gtk_fixed_set_has_window(GTK_FIXED(widget), true);
fc9ab22a 342#endif
e56307d3
PC
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);
e56307d3
PC
359 return widget;
360}
361
3b7067a0 362void wxPizza::move(GtkWidget* widget, int x, int y, int width, int height)
e56307d3 363{
3b7067a0 364 for (const GList* p = m_children; p; p = p->next)
e56307d3 365 {
3b7067a0 366 wxPizzaChild* child = static_cast<wxPizzaChild*>(p->data);
e56307d3
PC
367 if (child->widget == widget)
368 {
3b7067a0
PC
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
e56307d3
PC
375 break;
376 }
377 }
378}
379
3b7067a0 380void wxPizza::put(GtkWidget* widget, int x, int y, int width, int height)
f089940f 381{
4775836d
VZ
382 // Re-parenting a TLW under a child window is possible at wx level but
383 // using a TLW as child at GTK+ level results in problems, so don't do it.
384 if (!gtk_widget_is_toplevel(GTK_WIDGET(widget)))
385 gtk_fixed_put(GTK_FIXED(this), widget, 0, 0);
3b7067a0
PC
386
387 wxPizzaChild* child = new wxPizzaChild;
388 child->widget = widget;
389 child->x = x;
390 child->y = y;
391 child->width = width;
392 child->height = height;
393 m_children = g_list_append(m_children, child);
f089940f
PC
394}
395
a8997071
PC
396struct AdjustData {
397 GdkWindow* window;
398 int dx, dy;
399};
400
401// Adjust allocations for all widgets using the GdkWindow which was just scrolled
402extern "C" {
403static void scroll_adjust(GtkWidget* widget, void* data)
404{
405 const AdjustData* p = static_cast<AdjustData*>(data);
9dc44eff
PC
406 GtkAllocation a;
407 gtk_widget_get_allocation(widget, &a);
408 a.x += p->dx;
409 a.y += p->dy;
410 gtk_widget_set_allocation(widget, &a);
03647350 411
9dc44eff 412 if (gtk_widget_get_window(widget) == p->window)
a8997071 413 {
a8997071
PC
414 // GtkFrame requires a queue_resize, otherwise parts of
415 // the frame newly exposed by the scroll are not drawn.
416 // To be safe, do it for all widgets.
417 gtk_widget_queue_resize_no_redraw(widget);
418 if (GTK_IS_CONTAINER(widget))
419 gtk_container_forall(GTK_CONTAINER(widget), scroll_adjust, data);
420 }
421}
422}
423
e56307d3
PC
424void wxPizza::scroll(int dx, int dy)
425{
426 GtkWidget* widget = GTK_WIDGET(this);
427 if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL)
428 dx = -dx;
429 m_scroll_x -= dx;
430 m_scroll_y -= dy;
9dc44eff
PC
431 GdkWindow* window = gtk_widget_get_window(widget);
432 if (window)
e56307d3 433 {
9dc44eff 434 gdk_window_scroll(window, dx, dy);
a8997071
PC
435 // Adjust child allocations. Doing a queue_resize on the children is not
436 // enough, sometimes they redraw in the wrong place during fast scrolling.
9dc44eff 437 AdjustData data = { window, dx, dy };
a8997071 438 gtk_container_forall(GTK_CONTAINER(widget), scroll_adjust, &data);
e56307d3
PC
439 }
440}
441
9dc44eff 442void wxPizza::get_border(GtkBorder& border)
e56307d3 443{
75f661bb 444#ifndef __WXUNIVERSAL__
9dc44eff
PC
445 if (m_windowStyle & wxBORDER_SIMPLE)
446 border.left = border.right = border.top = border.bottom = 1;
447 else if (m_windowStyle & (wxBORDER_RAISED | wxBORDER_SUNKEN | wxBORDER_THEME))
d7b63a01 448 {
9dc44eff
PC
449#ifdef __WXGTK3__
450 GtkStyleContext* sc;
451 if (m_windowStyle & (wxHSCROLL | wxVSCROLL))
452 sc = gtk_widget_get_style_context(wxGTKPrivate::GetTreeWidget());
453 else
454 sc = gtk_widget_get_style_context(wxGTKPrivate::GetEntryWidget());
455
456 gtk_style_context_get_border(sc, GTK_STATE_FLAG_NORMAL, &border);
457#else // !__WXGTK3__
458 GtkStyle* style;
459 if (m_windowStyle & (wxHSCROLL | wxVSCROLL))
460 style = gtk_widget_get_style(wxGTKPrivate::GetTreeWidget());
461 else
462 style = gtk_widget_get_style(wxGTKPrivate::GetEntryWidget());
463
464 border.left = border.right = style->xthickness;
465 border.top = border.bottom = style->ythickness;
466#endif // !__WXGTK3__
d7b63a01 467 }
03647350 468 else
9dc44eff 469#endif // !__WXUNIVERSAL__
e56307d3 470 {
9dc44eff 471 border.left = border.right = border.top = border.bottom = 0;
e56307d3
PC
472 }
473}