]> git.saurik.com Git - wxWidgets.git/blame - src/gtk/win_gtk.cpp
Return NULL from wxWindow::GetCapture() when the capture is being lost.
[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
e56307d3
PC
5// Copyright: (c) 2007 Paul Cornett
6// Licence: wxWindows licence
7///////////////////////////////////////////////////////////////////////////////
8
e762ef8f
VZ
9#include "wx/wxprec.h"
10
e56307d3 11#include "wx/defs.h"
9dc44eff
PC
12
13#include <gtk/gtk.h>
bbd92d1d 14#include "wx/gtk/private/win_gtk.h"
e56307d3 15
9dc44eff
PC
16#include "wx/gtk/private.h"
17#include "wx/gtk/private/gtk2-compat.h"
18
e56307d3
PC
19/*
20wxPizza is a custom GTK+ widget derived from GtkFixed. A custom widget
21is needed to adapt GTK+ to wxWidgets needs in 3 areas: scrolling, window
22borders, and RTL.
23
24For scrolling, the "set_scroll_adjustments" signal is implemented
25to make wxPizza appear scrollable to GTK+, allowing it to be put in a
26GtkScrolledWindow. Child widget positions are adjusted for the scrolling
375efc1f 27position in size_allocate.
e56307d3 28
75f661bb
PC
29For borders, space is reserved in realize and size_allocate. The border is
30drawn on wxPizza's parent GdkWindow.
e56307d3
PC
31
32For RTL, child widget positions are mirrored in size_allocate.
33*/
34
3b7067a0
PC
35struct wxPizzaChild
36{
37 GtkWidget* widget;
38 int x, y, width, height;
39};
40
e56307d3
PC
41static GtkWidgetClass* parent_class;
42
9dc44eff
PC
43#ifdef __WXGTK3__
44enum {
45 PROP_0,
46 PROP_HADJUSTMENT,
47 PROP_VADJUSTMENT,
48 PROP_HSCROLL_POLICY,
49 PROP_VSCROLL_POLICY
50};
51#endif
52
e56307d3
PC
53extern "C" {
54
55struct wxPizzaClass
56{
57 GtkFixedClass parent;
9dc44eff 58#ifndef __WXGTK3__
e56307d3 59 void (*set_scroll_adjustments)(GtkWidget*, GtkAdjustment*, GtkAdjustment*);
9dc44eff 60#endif
e56307d3
PC
61};
62
9dc44eff 63static void pizza_size_allocate(GtkWidget* widget, GtkAllocation* alloc)
e56307d3 64{
e56307d3 65 wxPizza* pizza = WX_PIZZA(widget);
9dc44eff
PC
66 GtkBorder border;
67 pizza->get_border(border);
68 int w = alloc->width - border.left - border.right;
e56307d3
PC
69 if (w < 0) w = 0;
70
030f4112 71 if (gtk_widget_get_realized(widget))
e56307d3 72 {
9dc44eff 73 int h = alloc->height - border.top - border.bottom;
e56307d3 74 if (h < 0) h = 0;
9dc44eff
PC
75 const int x = alloc->x + border.left;
76 const int y = alloc->y + border.top;
e56307d3 77
030f4112
PC
78 GdkWindow* window = gtk_widget_get_window(widget);
79 int old_x, old_y;
80 gdk_window_get_position(window, &old_x, &old_y);
375efc1f 81
030f4112
PC
82 if (x != old_x || y != old_y ||
83 w != gdk_window_get_width(window) || h != gdk_window_get_height(window))
e56307d3 84 {
030f4112
PC
85 gdk_window_move_resize(window, x, y, w, h);
86
9dc44eff 87 if (border.left + border.right + border.top + border.bottom)
030f4112
PC
88 {
89 // old and new border areas need to be invalidated,
90 // otherwise they will not be erased/redrawn properly
9dc44eff
PC
91 GtkAllocation old_alloc;
92 gtk_widget_get_allocation(widget, &old_alloc);
030f4112 93 GdkWindow* parent = gtk_widget_get_parent_window(widget);
9dc44eff 94 gdk_window_invalidate_rect(parent, &old_alloc, false);
030f4112
PC
95 gdk_window_invalidate_rect(parent, alloc, false);
96 }
e56307d3
PC
97 }
98 }
7456fe19 99
9dc44eff 100 gtk_widget_set_allocation(widget, alloc);
03647350 101
e56307d3 102 // adjust child positions
3b7067a0 103 for (const GList* p = pizza->m_children; p; p = p->next)
e56307d3 104 {
3b7067a0 105 const wxPizzaChild* child = static_cast<wxPizzaChild*>(p->data);
fc9ab22a 106 if (gtk_widget_get_visible(child->widget))
e56307d3
PC
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;
3b7067a0
PC
114 child_alloc.width = child->width;
115 child_alloc.height = child->height;
e56307d3 116 if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL)
e56307d3 117 child_alloc.x = w - child_alloc.x - child_alloc.width;
18319143 118 gtk_widget_size_allocate(child->widget, &child_alloc);
e56307d3
PC
119 }
120 }
121}
122
9dc44eff 123static void pizza_realize(GtkWidget* widget)
e56307d3
PC
124{
125 parent_class->realize(widget);
126
127 wxPizza* pizza = WX_PIZZA(widget);
9dc44eff 128 if (pizza->m_windowStyle & wxPizza::BORDER_STYLES)
e56307d3 129 {
9dc44eff
PC
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;
e56307d3
PC
138 if (w < 0) w = 0;
139 if (h < 0) h = 0;
9dc44eff 140 gdk_window_move_resize(gtk_widget_get_window(widget), x, y, w, h);
e56307d3 141 }
e56307d3
PC
142}
143
9dc44eff 144static void pizza_show(GtkWidget* widget)
6e7cf3bd 145{
9dc44eff
PC
146 GtkWidget* parent = gtk_widget_get_parent(widget);
147 if (parent && (WX_PIZZA(widget)->m_windowStyle & wxPizza::BORDER_STYLES))
6e7cf3bd
PC
148 {
149 // invalidate whole allocation so borders will be drawn properly
9dc44eff
PC
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);
6e7cf3bd
PC
153 }
154
155 parent_class->show(widget);
156}
157
9dc44eff 158static void pizza_hide(GtkWidget* widget)
6e7cf3bd 159{
9dc44eff
PC
160 GtkWidget* parent = gtk_widget_get_parent(widget);
161 if (parent && (WX_PIZZA(widget)->m_windowStyle & wxPizza::BORDER_STYLES))
6e7cf3bd
PC
162 {
163 // invalidate whole allocation so borders will be erased properly
9dc44eff
PC
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);
6e7cf3bd
PC
167 }
168
169 parent_class->hide(widget);
170}
171
3b7067a0
PC
172static void pizza_add(GtkContainer* container, GtkWidget* widget)
173{
174 WX_PIZZA(container)->put(widget, 0, 0, 1, 1);
175}
176
177static 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
9dc44eff
PC
194#ifdef __WXGTK3__
195static void pizza_get_preferred_width(GtkWidget* widget, int* minimum, int* natural)
196{
197 *minimum = 0;
198 gtk_widget_get_size_request(widget, natural, NULL);
199 if (*natural < 0)
200 *natural = 0;
201}
202
203static void pizza_get_preferred_height(GtkWidget* widget, int* minimum, int* natural)
204{
205 *minimum = 0;
206 gtk_widget_get_size_request(widget, NULL, natural);
207 if (*natural < 0)
208 *natural = 0;
209}
210
211// Needed to implement GtkScrollable interface, but we don't care about the
212// properties. wxWindowGTK handles the adjustments and scroll policy.
213static void pizza_get_property(GObject*, guint, GValue*, GParamSpec*)
214{
215}
216
217static void pizza_set_property(GObject*, guint, const GValue*, GParamSpec*)
218{
219}
220#else
e56307d3 221// not used, but needs to exist so gtk_widget_set_scroll_adjustments will work
9dc44eff 222static void pizza_set_scroll_adjustments(GtkWidget*, GtkAdjustment*, GtkAdjustment*)
e56307d3
PC
223{
224}
225
226// Marshaller needed for set_scroll_adjustments signal,
227// generated with GLib-2.4.6 glib-genmarshal
228#define g_marshal_value_peek_object(v) g_value_get_object (v)
229static void
230g_cclosure_user_marshal_VOID__OBJECT_OBJECT (GClosure *closure,
231 GValue * /*return_value*/,
232 guint n_param_values,
233 const GValue *param_values,
234 gpointer /*invocation_hint*/,
235 gpointer marshal_data)
236{
237 typedef void (*GMarshalFunc_VOID__OBJECT_OBJECT) (gpointer data1,
238 gpointer arg_1,
239 gpointer arg_2,
240 gpointer data2);
241 register GMarshalFunc_VOID__OBJECT_OBJECT callback;
242 register GCClosure *cc = (GCClosure*) closure;
243 register gpointer data1, data2;
244
245 g_return_if_fail (n_param_values == 3);
246
247 if (G_CCLOSURE_SWAP_DATA (closure))
248 {
249 data1 = closure->data;
250 data2 = g_value_peek_pointer (param_values + 0);
251 }
252 else
253 {
254 data1 = g_value_peek_pointer (param_values + 0);
255 data2 = closure->data;
256 }
257 callback = (GMarshalFunc_VOID__OBJECT_OBJECT) (marshal_data ? marshal_data : cc->callback);
258
259 callback (data1,
260 g_marshal_value_peek_object (param_values + 1),
261 g_marshal_value_peek_object (param_values + 2),
262 data2);
263}
9dc44eff 264#endif
e56307d3
PC
265
266static void class_init(void* g_class, void*)
267{
268 GtkWidgetClass* widget_class = (GtkWidgetClass*)g_class;
9dc44eff
PC
269 widget_class->size_allocate = pizza_size_allocate;
270 widget_class->realize = pizza_realize;
271 widget_class->show = pizza_show;
272 widget_class->hide = pizza_hide;
3b7067a0
PC
273 GtkContainerClass* container_class = (GtkContainerClass*)g_class;
274 container_class->add = pizza_add;
275 container_class->remove = pizza_remove;
e56307d3 276
9dc44eff
PC
277#ifdef __WXGTK3__
278 widget_class->get_preferred_width = pizza_get_preferred_width;
279 widget_class->get_preferred_height = pizza_get_preferred_height;
280 GObjectClass *gobject_class = G_OBJECT_CLASS(g_class);
281 gobject_class->set_property = pizza_set_property;
282 gobject_class->get_property = pizza_get_property;
283 g_object_class_override_property(gobject_class, PROP_HADJUSTMENT, "hadjustment");
284 g_object_class_override_property(gobject_class, PROP_VADJUSTMENT, "vadjustment");
285 g_object_class_override_property(gobject_class, PROP_HSCROLL_POLICY, "hscroll-policy");
286 g_object_class_override_property(gobject_class, PROP_VSCROLL_POLICY, "vscroll-policy");
287#else
288 wxPizzaClass* klass = static_cast<wxPizzaClass*>(g_class);
e56307d3 289 // needed to make widget appear scrollable to GTK+
9dc44eff 290 klass->set_scroll_adjustments = pizza_set_scroll_adjustments;
e56307d3
PC
291 widget_class->set_scroll_adjustments_signal =
292 g_signal_new(
293 "set_scroll_adjustments",
294 G_TYPE_FROM_CLASS(g_class),
295 G_SIGNAL_RUN_LAST,
296 G_STRUCT_OFFSET(wxPizzaClass, set_scroll_adjustments),
297 NULL, NULL,
298 g_cclosure_user_marshal_VOID__OBJECT_OBJECT,
299 G_TYPE_NONE, 2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT);
9dc44eff 300#endif
e56307d3
PC
301 parent_class = GTK_WIDGET_CLASS(g_type_class_peek_parent(g_class));
302}
303
304} // extern "C"
305
306GType wxPizza::type()
307{
9dc44eff 308 static GType type;
e56307d3
PC
309 if (type == 0)
310 {
311 const GTypeInfo info = {
312 sizeof(wxPizzaClass),
313 NULL, NULL,
314 class_init,
315 NULL, NULL,
316 sizeof(wxPizza), 0,
317 NULL, NULL
318 };
319 type = g_type_register_static(
320 GTK_TYPE_FIXED, "wxPizza", &info, GTypeFlags(0));
9dc44eff
PC
321#ifdef __WXGTK3__
322 const GInterfaceInfo interface_info = { NULL, NULL, NULL };
323 g_type_add_interface_static(type, GTK_TYPE_SCROLLABLE, &interface_info);
324#endif
e56307d3
PC
325 }
326 return type;
327}
328
0f52f610 329GtkWidget* wxPizza::New(long windowStyle)
e56307d3
PC
330{
331 GtkWidget* widget = GTK_WIDGET(g_object_new(type(), NULL));
332 wxPizza* pizza = WX_PIZZA(widget);
3b7067a0 333 pizza->m_children = NULL;
e56307d3
PC
334 pizza->m_scroll_x = 0;
335 pizza->m_scroll_y = 0;
9dc44eff
PC
336 pizza->m_windowStyle = windowStyle;
337#ifdef __WXGTK3__
fc9ab22a
VS
338 gtk_widget_set_has_window(widget, true);
339#else
e56307d3 340 gtk_fixed_set_has_window(GTK_FIXED(widget), true);
fc9ab22a 341#endif
e56307d3
PC
342 gtk_widget_add_events(widget,
343 GDK_EXPOSURE_MASK |
344 GDK_SCROLL_MASK |
345 GDK_POINTER_MOTION_MASK |
346 GDK_POINTER_MOTION_HINT_MASK |
347 GDK_BUTTON_MOTION_MASK |
348 GDK_BUTTON1_MOTION_MASK |
349 GDK_BUTTON2_MOTION_MASK |
350 GDK_BUTTON3_MOTION_MASK |
351 GDK_BUTTON_PRESS_MASK |
352 GDK_BUTTON_RELEASE_MASK |
353 GDK_KEY_PRESS_MASK |
354 GDK_KEY_RELEASE_MASK |
355 GDK_ENTER_NOTIFY_MASK |
356 GDK_LEAVE_NOTIFY_MASK |
357 GDK_FOCUS_CHANGE_MASK);
e56307d3
PC
358 return widget;
359}
360
3b7067a0 361void wxPizza::move(GtkWidget* widget, int x, int y, int width, int height)
e56307d3 362{
3b7067a0 363 for (const GList* p = m_children; p; p = p->next)
e56307d3 364 {
3b7067a0 365 wxPizzaChild* child = static_cast<wxPizzaChild*>(p->data);
e56307d3
PC
366 if (child->widget == widget)
367 {
3b7067a0
PC
368 child->x = x;
369 child->y = y;
370 child->width = width;
371 child->height = height;
372 // normally a queue-resize would be needed here, but we know
373 // wxWindowGTK::DoMoveWindow() will take care of it
e56307d3
PC
374 break;
375 }
376 }
377}
378
3b7067a0 379void wxPizza::put(GtkWidget* widget, int x, int y, int width, int height)
f089940f 380{
4775836d
VZ
381 // Re-parenting a TLW under a child window is possible at wx level but
382 // using a TLW as child at GTK+ level results in problems, so don't do it.
659d10ca 383#if GTK_CHECK_VERSION(2,19,3)
4775836d 384 if (!gtk_widget_is_toplevel(GTK_WIDGET(widget)))
659d10ca
JS
385#else
386 if (!GTK_WIDGET_TOPLEVEL(GTK_WIDGET(widget)))
387#endif
4775836d 388 gtk_fixed_put(GTK_FIXED(this), widget, 0, 0);
3b7067a0
PC
389
390 wxPizzaChild* child = new wxPizzaChild;
391 child->widget = widget;
392 child->x = x;
393 child->y = y;
394 child->width = width;
395 child->height = height;
396 m_children = g_list_append(m_children, child);
f089940f
PC
397}
398
a8997071
PC
399struct AdjustData {
400 GdkWindow* window;
401 int dx, dy;
402};
403
404// Adjust allocations for all widgets using the GdkWindow which was just scrolled
405extern "C" {
406static void scroll_adjust(GtkWidget* widget, void* data)
407{
408 const AdjustData* p = static_cast<AdjustData*>(data);
9dc44eff
PC
409 GtkAllocation a;
410 gtk_widget_get_allocation(widget, &a);
411 a.x += p->dx;
412 a.y += p->dy;
413 gtk_widget_set_allocation(widget, &a);
03647350 414
9dc44eff 415 if (gtk_widget_get_window(widget) == p->window)
a8997071 416 {
a8997071
PC
417 // GtkFrame requires a queue_resize, otherwise parts of
418 // the frame newly exposed by the scroll are not drawn.
419 // To be safe, do it for all widgets.
420 gtk_widget_queue_resize_no_redraw(widget);
421 if (GTK_IS_CONTAINER(widget))
422 gtk_container_forall(GTK_CONTAINER(widget), scroll_adjust, data);
423 }
424}
425}
426
e56307d3
PC
427void wxPizza::scroll(int dx, int dy)
428{
429 GtkWidget* widget = GTK_WIDGET(this);
430 if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL)
431 dx = -dx;
432 m_scroll_x -= dx;
433 m_scroll_y -= dy;
9dc44eff
PC
434 GdkWindow* window = gtk_widget_get_window(widget);
435 if (window)
e56307d3 436 {
9dc44eff 437 gdk_window_scroll(window, dx, dy);
a8997071
PC
438 // Adjust child allocations. Doing a queue_resize on the children is not
439 // enough, sometimes they redraw in the wrong place during fast scrolling.
9dc44eff 440 AdjustData data = { window, dx, dy };
a8997071 441 gtk_container_forall(GTK_CONTAINER(widget), scroll_adjust, &data);
e56307d3
PC
442 }
443}
444
9dc44eff 445void wxPizza::get_border(GtkBorder& border)
e56307d3 446{
75f661bb 447#ifndef __WXUNIVERSAL__
9dc44eff
PC
448 if (m_windowStyle & wxBORDER_SIMPLE)
449 border.left = border.right = border.top = border.bottom = 1;
450 else if (m_windowStyle & (wxBORDER_RAISED | wxBORDER_SUNKEN | wxBORDER_THEME))
d7b63a01 451 {
9dc44eff
PC
452#ifdef __WXGTK3__
453 GtkStyleContext* sc;
454 if (m_windowStyle & (wxHSCROLL | wxVSCROLL))
455 sc = gtk_widget_get_style_context(wxGTKPrivate::GetTreeWidget());
456 else
457 sc = gtk_widget_get_style_context(wxGTKPrivate::GetEntryWidget());
458
459 gtk_style_context_get_border(sc, GTK_STATE_FLAG_NORMAL, &border);
460#else // !__WXGTK3__
461 GtkStyle* style;
462 if (m_windowStyle & (wxHSCROLL | wxVSCROLL))
463 style = gtk_widget_get_style(wxGTKPrivate::GetTreeWidget());
464 else
465 style = gtk_widget_get_style(wxGTKPrivate::GetEntryWidget());
466
467 border.left = border.right = style->xthickness;
468 border.top = border.bottom = style->ythickness;
469#endif // !__WXGTK3__
d7b63a01 470 }
03647350 471 else
9dc44eff 472#endif // !__WXUNIVERSAL__
e56307d3 473 {
9dc44eff 474 border.left = border.right = border.top = border.bottom = 0;
e56307d3
PC
475 }
476}