| 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 | |
| 10 | #include "wx/defs.h" |
| 11 | #include "wx/gtk/private.h" |
| 12 | #include "wx/gtk/private/win_gtk.h" |
| 13 | |
| 14 | /* |
| 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 |
| 17 | borders, and RTL. |
| 18 | |
| 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. |
| 23 | |
| 24 | For borders, space is reserved in realize and size_allocate. The border is |
| 25 | drawn on wxPizza's parent GdkWindow. |
| 26 | |
| 27 | For RTL, child widget positions are mirrored in size_allocate. |
| 28 | */ |
| 29 | |
| 30 | static GtkWidgetClass* parent_class; |
| 31 | |
| 32 | extern "C" { |
| 33 | |
| 34 | struct wxPizzaClass |
| 35 | { |
| 36 | GtkFixedClass parent; |
| 37 | void (*set_scroll_adjustments)(GtkWidget*, GtkAdjustment*, GtkAdjustment*); |
| 38 | }; |
| 39 | |
| 40 | static void size_allocate(GtkWidget* widget, GtkAllocation* alloc) |
| 41 | { |
| 42 | const bool is_resize = |
| 43 | widget->allocation.width != alloc->width || |
| 44 | widget->allocation.height != alloc->height; |
| 45 | const bool is_move = |
| 46 | widget->allocation.x != alloc->x || |
| 47 | widget->allocation.y != alloc->y; |
| 48 | |
| 49 | wxPizza* pizza = WX_PIZZA(widget); |
| 50 | int border_x, border_y; |
| 51 | pizza->get_border_widths(border_x, border_y); |
| 52 | int w = alloc->width - 2 * border_x; |
| 53 | if (w < 0) w = 0; |
| 54 | |
| 55 | if (GTK_WIDGET_REALIZED(widget) && (is_move || is_resize)) |
| 56 | { |
| 57 | int h = alloc->height - 2 * border_y; |
| 58 | if (h < 0) h = 0; |
| 59 | |
| 60 | gdk_window_move_resize(widget->window, |
| 61 | alloc->x + border_x, alloc->y + border_y, w, h); |
| 62 | |
| 63 | if (is_resize && (border_x || border_y)) |
| 64 | { |
| 65 | // old and new border areas need to be invalidated, |
| 66 | // otherwise they will not be erased/redrawn properly |
| 67 | GdkWindow* parent = gtk_widget_get_parent_window(widget); |
| 68 | gdk_window_invalidate_rect(parent, &widget->allocation, false); |
| 69 | gdk_window_invalidate_rect(parent, alloc, false); |
| 70 | } |
| 71 | } |
| 72 | |
| 73 | widget->allocation = *alloc; |
| 74 | |
| 75 | // adjust child positions |
| 76 | for (const GList* list = pizza->m_fixed.children; list; list = list->next) |
| 77 | { |
| 78 | const GtkFixedChild* child = static_cast<GtkFixedChild*>(list->data); |
| 79 | if (GTK_WIDGET_VISIBLE(child->widget)) |
| 80 | { |
| 81 | GtkAllocation child_alloc; |
| 82 | // note that child positions do not take border into |
| 83 | // account, they need to be relative to widget->window, |
| 84 | // which has already been adjusted |
| 85 | child_alloc.x = child->x - pizza->m_scroll_x; |
| 86 | child_alloc.y = child->y - pizza->m_scroll_y; |
| 87 | GtkRequisition req; |
| 88 | gtk_widget_get_child_requisition(child->widget, &req); |
| 89 | child_alloc.width = req.width; |
| 90 | child_alloc.height = req.height; |
| 91 | if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL) |
| 92 | child_alloc.x = w - child_alloc.x - child_alloc.width; |
| 93 | gtk_widget_size_allocate(child->widget, &child_alloc); |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | static void realize(GtkWidget* widget) |
| 99 | { |
| 100 | parent_class->realize(widget); |
| 101 | |
| 102 | wxPizza* pizza = WX_PIZZA(widget); |
| 103 | if (pizza->m_border_style) |
| 104 | { |
| 105 | int border_x, border_y; |
| 106 | pizza->get_border_widths(border_x, border_y); |
| 107 | int x = widget->allocation.x + border_x; |
| 108 | int y = widget->allocation.y + border_y; |
| 109 | int w = widget->allocation.width - 2 * border_x; |
| 110 | int h = widget->allocation.height - 2 * border_y; |
| 111 | if (w < 0) w = 0; |
| 112 | if (h < 0) h = 0; |
| 113 | gdk_window_move_resize(widget->window, x, y, w, h); |
| 114 | } |
| 115 | } |
| 116 | |
| 117 | // not used, but needs to exist so gtk_widget_set_scroll_adjustments will work |
| 118 | static void set_scroll_adjustments(GtkWidget*, GtkAdjustment*, GtkAdjustment*) |
| 119 | { |
| 120 | } |
| 121 | |
| 122 | // Marshaller needed for set_scroll_adjustments signal, |
| 123 | // generated with GLib-2.4.6 glib-genmarshal |
| 124 | #define g_marshal_value_peek_object(v) g_value_get_object (v) |
| 125 | static void |
| 126 | g_cclosure_user_marshal_VOID__OBJECT_OBJECT (GClosure *closure, |
| 127 | GValue * /*return_value*/, |
| 128 | guint n_param_values, |
| 129 | const GValue *param_values, |
| 130 | gpointer /*invocation_hint*/, |
| 131 | gpointer marshal_data) |
| 132 | { |
| 133 | typedef void (*GMarshalFunc_VOID__OBJECT_OBJECT) (gpointer data1, |
| 134 | gpointer arg_1, |
| 135 | gpointer arg_2, |
| 136 | gpointer data2); |
| 137 | register GMarshalFunc_VOID__OBJECT_OBJECT callback; |
| 138 | register GCClosure *cc = (GCClosure*) closure; |
| 139 | register gpointer data1, data2; |
| 140 | |
| 141 | g_return_if_fail (n_param_values == 3); |
| 142 | |
| 143 | if (G_CCLOSURE_SWAP_DATA (closure)) |
| 144 | { |
| 145 | data1 = closure->data; |
| 146 | data2 = g_value_peek_pointer (param_values + 0); |
| 147 | } |
| 148 | else |
| 149 | { |
| 150 | data1 = g_value_peek_pointer (param_values + 0); |
| 151 | data2 = closure->data; |
| 152 | } |
| 153 | callback = (GMarshalFunc_VOID__OBJECT_OBJECT) (marshal_data ? marshal_data : cc->callback); |
| 154 | |
| 155 | callback (data1, |
| 156 | g_marshal_value_peek_object (param_values + 1), |
| 157 | g_marshal_value_peek_object (param_values + 2), |
| 158 | data2); |
| 159 | } |
| 160 | |
| 161 | static void class_init(void* g_class, void*) |
| 162 | { |
| 163 | GtkWidgetClass* widget_class = (GtkWidgetClass*)g_class; |
| 164 | widget_class->size_allocate = size_allocate; |
| 165 | widget_class->realize = realize; |
| 166 | wxPizzaClass* klass = (wxPizzaClass*)g_class; |
| 167 | |
| 168 | // needed to make widget appear scrollable to GTK+ |
| 169 | klass->set_scroll_adjustments = set_scroll_adjustments; |
| 170 | widget_class->set_scroll_adjustments_signal = |
| 171 | g_signal_new( |
| 172 | "set_scroll_adjustments", |
| 173 | G_TYPE_FROM_CLASS(g_class), |
| 174 | G_SIGNAL_RUN_LAST, |
| 175 | G_STRUCT_OFFSET(wxPizzaClass, set_scroll_adjustments), |
| 176 | NULL, NULL, |
| 177 | g_cclosure_user_marshal_VOID__OBJECT_OBJECT, |
| 178 | G_TYPE_NONE, 2, GTK_TYPE_ADJUSTMENT, GTK_TYPE_ADJUSTMENT); |
| 179 | |
| 180 | parent_class = GTK_WIDGET_CLASS(g_type_class_peek_parent(g_class)); |
| 181 | } |
| 182 | |
| 183 | } // extern "C" |
| 184 | |
| 185 | GType wxPizza::type() |
| 186 | { |
| 187 | static GtkType type; |
| 188 | if (type == 0) |
| 189 | { |
| 190 | const GTypeInfo info = { |
| 191 | sizeof(wxPizzaClass), |
| 192 | NULL, NULL, |
| 193 | class_init, |
| 194 | NULL, NULL, |
| 195 | sizeof(wxPizza), 0, |
| 196 | NULL, NULL |
| 197 | }; |
| 198 | type = g_type_register_static( |
| 199 | GTK_TYPE_FIXED, "wxPizza", &info, GTypeFlags(0)); |
| 200 | } |
| 201 | return type; |
| 202 | } |
| 203 | |
| 204 | GtkWidget* wxPizza::New(long windowStyle) |
| 205 | { |
| 206 | GtkWidget* widget = GTK_WIDGET(g_object_new(type(), NULL)); |
| 207 | wxPizza* pizza = WX_PIZZA(widget); |
| 208 | pizza->m_scroll_x = 0; |
| 209 | pizza->m_scroll_y = 0; |
| 210 | pizza->m_is_scrollable = (windowStyle & (wxHSCROLL | wxVSCROLL)) != 0; |
| 211 | // mask off border styles not useable with wxPizza |
| 212 | pizza->m_border_style = int(windowStyle & BORDER_STYLES); |
| 213 | gtk_fixed_set_has_window(GTK_FIXED(widget), true); |
| 214 | gtk_widget_add_events(widget, |
| 215 | GDK_EXPOSURE_MASK | |
| 216 | GDK_SCROLL_MASK | |
| 217 | GDK_POINTER_MOTION_MASK | |
| 218 | GDK_POINTER_MOTION_HINT_MASK | |
| 219 | GDK_BUTTON_MOTION_MASK | |
| 220 | GDK_BUTTON1_MOTION_MASK | |
| 221 | GDK_BUTTON2_MOTION_MASK | |
| 222 | GDK_BUTTON3_MOTION_MASK | |
| 223 | GDK_BUTTON_PRESS_MASK | |
| 224 | GDK_BUTTON_RELEASE_MASK | |
| 225 | GDK_KEY_PRESS_MASK | |
| 226 | GDK_KEY_RELEASE_MASK | |
| 227 | GDK_ENTER_NOTIFY_MASK | |
| 228 | GDK_LEAVE_NOTIFY_MASK | |
| 229 | GDK_FOCUS_CHANGE_MASK); |
| 230 | return widget; |
| 231 | } |
| 232 | |
| 233 | // gtk_fixed_move does not check for a change before issuing a queue_resize, |
| 234 | // we need to avoid that to prevent endless sizing loops, so check first |
| 235 | void wxPizza::move(GtkWidget* widget, int x, int y) |
| 236 | { |
| 237 | GtkFixed* fixed = &m_fixed; |
| 238 | for (const GList* list = fixed->children; list; list = list->next) |
| 239 | { |
| 240 | const GtkFixedChild* child = static_cast<GtkFixedChild*>(list->data); |
| 241 | if (child->widget == widget) |
| 242 | { |
| 243 | if (child->x != x || child->y != y) |
| 244 | gtk_fixed_move(fixed, widget, x, y); |
| 245 | break; |
| 246 | } |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | void wxPizza::put(GtkWidget* widget, int x, int y) |
| 251 | { |
| 252 | gtk_fixed_put(&m_fixed, widget, x, y); |
| 253 | } |
| 254 | |
| 255 | struct AdjustData { |
| 256 | GdkWindow* window; |
| 257 | int dx, dy; |
| 258 | }; |
| 259 | |
| 260 | // Adjust allocations for all widgets using the GdkWindow which was just scrolled |
| 261 | extern "C" { |
| 262 | static void scroll_adjust(GtkWidget* widget, void* data) |
| 263 | { |
| 264 | const AdjustData* p = static_cast<AdjustData*>(data); |
| 265 | widget->allocation.x += p->dx; |
| 266 | widget->allocation.y += p->dy; |
| 267 | |
| 268 | if (widget->window == p->window) |
| 269 | { |
| 270 | // GtkFrame requires a queue_resize, otherwise parts of |
| 271 | // the frame newly exposed by the scroll are not drawn. |
| 272 | // To be safe, do it for all widgets. |
| 273 | gtk_widget_queue_resize_no_redraw(widget); |
| 274 | if (GTK_IS_CONTAINER(widget)) |
| 275 | gtk_container_forall(GTK_CONTAINER(widget), scroll_adjust, data); |
| 276 | } |
| 277 | } |
| 278 | } |
| 279 | |
| 280 | void wxPizza::scroll(int dx, int dy) |
| 281 | { |
| 282 | GtkWidget* widget = GTK_WIDGET(this); |
| 283 | if (gtk_widget_get_direction(widget) == GTK_TEXT_DIR_RTL) |
| 284 | dx = -dx; |
| 285 | m_scroll_x -= dx; |
| 286 | m_scroll_y -= dy; |
| 287 | if (widget->window) |
| 288 | { |
| 289 | gdk_window_scroll(widget->window, dx, dy); |
| 290 | // Adjust child allocations. Doing a queue_resize on the children is not |
| 291 | // enough, sometimes they redraw in the wrong place during fast scrolling. |
| 292 | AdjustData data = { widget->window, dx, dy }; |
| 293 | gtk_container_forall(GTK_CONTAINER(widget), scroll_adjust, &data); |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | void wxPizza::get_border_widths(int& x, int& y) |
| 298 | { |
| 299 | x = y = 0; |
| 300 | if (m_border_style == 0) |
| 301 | return; |
| 302 | |
| 303 | #ifndef __WXUNIVERSAL__ |
| 304 | if (m_border_style & wxBORDER_SIMPLE) |
| 305 | x = y = 1; |
| 306 | else if (m_is_scrollable /* || (m_border_style & wxBORDER_THEME) */) |
| 307 | { |
| 308 | GtkWidget *style_widget = wxGTKPrivate::GetTreeWidget(); |
| 309 | |
| 310 | if (style_widget->style) |
| 311 | { |
| 312 | x = style_widget->style->xthickness; |
| 313 | y = style_widget->style->ythickness; |
| 314 | } |
| 315 | } |
| 316 | else |
| 317 | { |
| 318 | GtkWidget *style_widget = wxGTKPrivate::GetEntryWidget(); |
| 319 | |
| 320 | if (style_widget->style) |
| 321 | { |
| 322 | x = style_widget->style->xthickness; |
| 323 | y = style_widget->style->ythickness; |
| 324 | } |
| 325 | } |
| 326 | #endif |
| 327 | } |