1 /*
2 * Nautilus-Actions
3 * A Nautilus extension which offers configurable context menu actions.
4 *
5 * Copyright (C) 2005 The GNOME Foundation
6 * Copyright (C) 2006-2008 Frederic Ruaudel and others (see AUTHORS)
7 * Copyright (C) 2009-2014 Pierre Wieser and others (see AUTHORS)
8 *
9 * Nautilus-Actions is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of
12 * the License, or (at your option) any later version.
13 *
14 * Nautilus-Actions is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with Nautilus-Actions; see the file COPYING. If not, see
21 * <http://www.gnu.org/licenses/>.
22 *
23 * Authors:
24 * Frederic Ruaudel <grumz@grumz.net>
25 * Rodrigo Moya <rodrigo@gnome-db.org>
26 * Pierre Wieser <pwieser@trychlos.org>
27 * ... and many others (see AUTHORS)
28 */
29
30 #ifdef HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33
34 #include <string.h>
35
36 #include <core/na-gtk-utils.h>
37 #include <core/na-updater.h>
38
39 #include "base-gtk-utils.h"
40
41 #define DEFAULT_WIDTH 22
42
43 typedef struct {
44 GtkWidget *table;
45 guint rows;
46 guint ir;
47 guint columns;
48 guint ic;
49 GtkWidget *grid;
50 }
51 TableToGridData;
52
53 #if GTK_CHECK_VERSION( 3,0,0 )
54 static void table_to_grid_foreach_cb( GtkWidget *widget, TableToGridData *ttg );
55 #endif
56
57 /**
58 * base_gtk_utils_position_window:
59 * @window: this #BaseWindow-derived window.
60 * @wsp_name: the string which handles the window size and position in user preferences.
61 *
62 * Position the specified window on the screen.
63 *
64 * A window position is stored as a list of integers "x,y,width,height".
65 */
66 void
base_gtk_utils_restore_window_position(const BaseWindow * window,const gchar * wsp_name)67 base_gtk_utils_restore_window_position( const BaseWindow *window, const gchar *wsp_name )
68 {
69 GtkWindow *toplevel;
70
71 g_return_if_fail( BASE_IS_WINDOW( window ));
72 g_return_if_fail( wsp_name && strlen( wsp_name ));
73
74 toplevel = base_window_get_gtk_toplevel( window );
75 g_return_if_fail( GTK_IS_WINDOW( toplevel ));
76
77 na_gtk_utils_restore_window_position( toplevel, wsp_name );
78 }
79
80 /**
81 * base_gtk_utils_save_window_position:
82 * @window: this #BaseWindow-derived window.
83 * @wsp_name: the string which handles the window size and position in user preferences.
84 *
85 * Save the size and position of the specified window.
86 */
87 void
base_gtk_utils_save_window_position(const BaseWindow * window,const gchar * wsp_name)88 base_gtk_utils_save_window_position( const BaseWindow *window, const gchar *wsp_name )
89 {
90 GtkWindow *toplevel;
91
92 g_return_if_fail( BASE_IS_WINDOW( window ));
93 g_return_if_fail( wsp_name && strlen( wsp_name ));
94
95 toplevel = base_window_get_gtk_toplevel( window );
96 g_return_if_fail( GTK_IS_WINDOW( toplevel ));
97
98 na_gtk_utils_save_window_position( toplevel, wsp_name );
99 }
100
101 /**
102 * base_gtk_utils_set_editable:
103 * @widget: the #GtkWdiget.
104 * @editable: whether the @widget is editable or not.
105 *
106 * Try to set a visual indication of whether the @widget is editable or not.
107 *
108 * Having a GtkWidget should be enough, but we also deal with a GtkTreeViewColumn.
109 * So the most-bottom common ancestor is just GObject (since GtkObject having been
110 * deprecated in Gtk+-3.0)
111 */
112 void
base_gtk_utils_set_editable(GObject * widget,gboolean editable)113 base_gtk_utils_set_editable( GObject *widget, gboolean editable )
114 {
115 na_gtk_utils_set_editable( widget, editable );
116 }
117
118 /**
119 * base_gtk_utils_radio_set_initial_state:
120 * @button: the #GtkRadioButton button which is initially active.
121 * @handler: the corresponding "toggled" handler.
122 * @user_data: the user data associated to the handler.
123 * @editable: whether this radio button group is editable.
124 * @sensitive: whether this radio button group is sensitive.
125 *
126 * This function should be called for the button which is initially active
127 * inside of a radio button group when the radio group may happen to not be
128 * editable.
129 * This function should be called only once for the radio button group.
130 *
131 * It does the following operations:
132 * - set the button as active
133 * - set other buttons of the radio button group as inactive
134 * - set all buttons of radio button group as @editable
135 *
136 * The initially active @button, along with its @handler, are recorded
137 * as properties of the radio button group (actually as properties of each
138 * radio button of the group), so that they can later be used to reset the
139 * initial state.
140 */
141 void
base_gtk_utils_radio_set_initial_state(GtkRadioButton * button,GCallback handler,void * user_data,gboolean editable,gboolean sensitive)142 base_gtk_utils_radio_set_initial_state( GtkRadioButton *button,
143 GCallback handler, void *user_data, gboolean editable, gboolean sensitive )
144 {
145 na_gtk_utils_radio_set_initial_state( button, handler, user_data, editable, sensitive );
146 }
147
148 /**
149 * base_gtk_utils_radio_reset_initial_state:
150 * @button: the #GtkRadioButton being toggled.
151 * @handler: the corresponding "toggled" handler.
152 * @data: data associated with the @handler callback.
153 *
154 * When clicking on a read-only radio button, this function ensures that
155 * the radio button is not modified. It may be called whether the radio
156 * button group is editable or not (does nothing if group is actually
157 * editable).
158 */
159 void
base_gtk_utils_radio_reset_initial_state(GtkRadioButton * button,GCallback handler)160 base_gtk_utils_radio_reset_initial_state( GtkRadioButton *button, GCallback handler )
161 {
162 na_gtk_utils_radio_reset_initial_state( button, handler );
163 }
164
165 /**
166 * base_gtk_utils_toggle_set_initial_state:
167 * @button: the #GtkToggleButton button.
168 * @handler: the corresponding "toggled" handler.
169 * @window: the toplevel #BaseWindow which embeds the button;
170 * it will be passed as user_data when connecting the signal.
171 * @active: whether the check button is initially active (checked).
172 * @editable: whether this radio button group is editable.
173 * @sensitive: whether this radio button group is sensitive.
174 *
175 * This function should be called for a check button which may happen to be
176 * read-only..
177 *
178 * It does the following operations:
179 * - connect the 'toggled' handler to the button
180 * - set the button as active or inactive depending of @active
181 * - set the button as editable or not depending of @editable
182 * - set the button as sensitive or not depending of @sensitive
183 * - explictely triggers the 'toggled' handler
184 */
185 void
base_gtk_utils_toggle_set_initial_state(BaseWindow * window,const gchar * button_name,GCallback handler,gboolean active,gboolean editable,gboolean sensitive)186 base_gtk_utils_toggle_set_initial_state( BaseWindow *window,
187 const gchar *button_name, GCallback handler,
188 gboolean active, gboolean editable, gboolean sensitive )
189 {
190 typedef void ( *toggle_handler )( GtkToggleButton *, BaseWindow * );
191 GtkToggleButton *button;
192
193 button = GTK_TOGGLE_BUTTON( base_window_get_widget( window, button_name ));
194
195 if( button ){
196 base_window_signal_connect( window, G_OBJECT( button ), "toggled", handler );
197
198 g_object_set_data( G_OBJECT( button ), NA_TOGGLE_DATA_HANDLER, handler );
199 g_object_set_data( G_OBJECT( button ), NA_TOGGLE_DATA_USER_DATA, window );
200 g_object_set_data( G_OBJECT( button ), NA_TOGGLE_DATA_EDITABLE, GUINT_TO_POINTER( editable ));
201
202 base_gtk_utils_set_editable( G_OBJECT( button ), editable );
203 gtk_widget_set_sensitive( GTK_WIDGET( button ), sensitive );
204 gtk_toggle_button_set_active( button, active );
205
206 ( *( toggle_handler ) handler )( button, window );
207 }
208 }
209
210 /**
211 * base_gtk_utils_toggle_reset_initial_state:
212 * @button: the #GtkToggleButton check button.
213 *
214 * When clicking on a read-only check button, this function ensures that
215 * the check button is not modified. It may be called whether the button
216 * is editable or not (does nothing if button is actually editable).
217 */
218 void
base_gtk_utils_toggle_reset_initial_state(GtkToggleButton * button)219 base_gtk_utils_toggle_reset_initial_state( GtkToggleButton *button )
220 {
221 gboolean editable;
222 GCallback handler;
223 gpointer user_data;
224 gboolean active;
225
226 editable = ( gboolean ) GPOINTER_TO_UINT( g_object_get_data( G_OBJECT( button ), NA_TOGGLE_DATA_EDITABLE ));
227
228 if( !editable ){
229 active = gtk_toggle_button_get_active( button );
230 handler = G_CALLBACK( g_object_get_data( G_OBJECT( button ), NA_TOGGLE_DATA_HANDLER ));
231 user_data = g_object_get_data( G_OBJECT( button ), NA_TOGGLE_DATA_USER_DATA );
232
233 g_signal_handlers_block_by_func(( gpointer ) button, handler, user_data );
234 gtk_toggle_button_set_active( button, !active );
235 g_signal_handlers_unblock_by_func(( gpointer ) button, handler, user_data );
236 }
237 }
238
239 /**
240 * base_gtk_utils_get_pixbuf:
241 * @name: the name of the file or an icon.
242 * @widget: the widget on which the image should be rendered.
243 * @size: the desired size.
244 *
245 * Returns a pixbuf for the given widget.
246 */
247 GdkPixbuf *
base_gtk_utils_get_pixbuf(const gchar * name,GtkWidget * widget,GtkIconSize size)248 base_gtk_utils_get_pixbuf( const gchar *name, GtkWidget *widget, GtkIconSize size )
249 {
250 static const gchar *thisfn = "base_gtk_utils_get_pixbuf";
251 GdkPixbuf* pixbuf;
252 GError *error;
253 gint width, height;
254 GtkIconTheme *icon_theme;
255
256 error = NULL;
257 pixbuf = NULL;
258
259 if( !gtk_icon_size_lookup( size, &width, &height )){
260 width = DEFAULT_WIDTH;
261 height = DEFAULT_HEIGHT;
262 }
263
264 if( name && strlen( name )){
265 if( g_path_is_absolute( name )){
266 pixbuf = gdk_pixbuf_new_from_file_at_size( name, width, height, &error );
267 if( error ){
268 if( error->code != G_FILE_ERROR_NOENT ){
269 g_warning( "%s: gdk_pixbuf_new_from_file_at_size: name=%s, error=%s (%d)", thisfn, name, error->message, error->code );
270 }
271 g_error_free( error );
272 error = NULL;
273 pixbuf = NULL;
274 }
275
276 } else {
277 /* gtk_widget_render_icon() is deprecated since Gtk+ 3.0
278 * see http://library.gnome.org/devel/gtk/unstable/GtkWidget.html#gtk-widget-render-icon
279 * and http://git.gnome.org/browse/gtk+/commit/?id=07eeae15825403037b7df139acf9bfa104d5559d
280 */
281 #if GTK_CHECK_VERSION( 2, 91, 7 )
282 pixbuf = gtk_widget_render_icon_pixbuf( widget, name, size );
283 #else
284 pixbuf = gtk_widget_render_icon( widget, name, size, NULL );
285 #endif
286 if( !pixbuf ){
287 icon_theme = gtk_icon_theme_get_default();
288 pixbuf = gtk_icon_theme_load_icon(
289 icon_theme, name, width, GTK_ICON_LOOKUP_GENERIC_FALLBACK, &error );
290 if( error ){
291 /* it happens that the message "Icon 'xxxx' not present in theme"
292 * is generated with a domain of 'gtk-icon-theme-error-quark' and
293 * an error code of zero - it seems difficult to just test zero
294 * so does not display warning, but just debug
295 */
296 g_debug( "%s: %s (%s:%d)",
297 thisfn, error->message, g_quark_to_string( error->domain ), error->code );
298 g_error_free( error );
299 }
300 }
301 }
302 }
303
304 if( !pixbuf ){
305 g_debug( "%s: null pixbuf, loading transparent image", thisfn );
306 pixbuf = gdk_pixbuf_new_from_file_at_size( PKGUIDIR "/transparent.png", width, height, NULL );
307 }
308
309 return( pixbuf );
310 }
311
312 /**
313 * base_gtk_utils_render:
314 * @name: the name of the file or an icon, or %NULL.
315 * @widget: the widget on which the image should be rendered.
316 * @size: the desired size.
317 *
318 * Displays the (maybe themed) image on the given widget.
319 */
320 void
base_gtk_utils_render(const gchar * name,GtkImage * widget,GtkIconSize size)321 base_gtk_utils_render( const gchar *name, GtkImage *widget, GtkIconSize size )
322 {
323 static const gchar *thisfn = "base_gtk_utils_render";
324 GdkPixbuf* pixbuf;
325 gint width, height;
326
327 g_debug( "%s: name=%s, widget=%p, size=%d", thisfn, name, ( void * ) widget, size );
328
329 if( name ){
330 pixbuf = base_gtk_utils_get_pixbuf( name, GTK_WIDGET( widget ), size );
331
332 } else {
333 if( !gtk_icon_size_lookup( size, &width, &height )){
334 width = DEFAULT_WIDTH;
335 height = DEFAULT_HEIGHT;
336 }
337 pixbuf = gdk_pixbuf_new_from_file_at_size( PKGUIDIR "/transparent.png", width, height, NULL );
338 }
339
340 if( pixbuf ){
341 gtk_image_set_from_pixbuf( widget, pixbuf );
342 g_object_unref( pixbuf );
343 }
344 }
345
346 /**
347 * base_gtk_utils_select_file:
348 * @window: the #BaseWindow which will be the parent of the dialog box.
349 * @title: the title of the dialog box.
350 * @wsp_name: the name of the dialog box in Preferences to read/write
351 * its size and position.
352 * @entry: the #GtkEntry which is associated with the selected file.
353 * @entry_name: the name of the entry in Preferences to be read/written.
354 *
355 * Opens a #GtkFileChooserDialog and let the user choose an existing file
356 * -> choose and display an existing file name
357 * -> record the dirname URI.
358 *
359 * If the user validates its selection, the chosen file pathname will be
360 * written in the @entry #GtkEntry, while the corresponding dirname
361 * URI will be written as @entry_name in Preferences.
362 */
363 void
base_gtk_utils_select_file(BaseWindow * window,const gchar * title,const gchar * wsp_name,GtkWidget * entry,const gchar * entry_name)364 base_gtk_utils_select_file( BaseWindow *window,
365 const gchar *title, const gchar *wsp_name,
366 GtkWidget *entry, const gchar *entry_name )
367 {
368 base_gtk_utils_select_file_with_preview(
369 window, title, wsp_name, entry, entry_name, NULL );
370 }
371
372 /**
373 * base_gtk_utils_select_file_with_preview:
374 * @window: the #BaseWindow which will be the parent of the dialog box.
375 * @title: the title of the dialog box.
376 * @wsp_name: the name of the dialog box in Preferences to read/write
377 * its size and position.
378 * @entry: the #GtkEntry which is associated with the selected file.
379 * @entry_name: the name of the entry in Preferences to be read/written.
380 * @update_preview_cb: the callback function in charge of updating the
381 * preview widget. May be NULL.
382 *
383 * Opens a #GtkFileChooserDialog and let the user choose an existing file
384 * -> choose and display an existing file name
385 * -> record the dirname URI.
386 *
387 * If the user validates its selection, the chosen file pathname will be
388 * written in the @entry #GtkEntry, while the corresponding dirname
389 * URI will be written as @entry_name in Preferences.
390 */
391 void
base_gtk_utils_select_file_with_preview(BaseWindow * window,const gchar * title,const gchar * wsp_name,GtkWidget * entry,const gchar * entry_name,GCallback update_preview_cb)392 base_gtk_utils_select_file_with_preview( BaseWindow *window,
393 const gchar *title, const gchar *wsp_name,
394 GtkWidget *entry, const gchar *entry_name,
395 GCallback update_preview_cb )
396 {
397 GtkWindow *toplevel;
398 GtkWidget *dialog;
399 const gchar *text;
400 gchar *filename, *uri;
401 GtkWidget *preview;
402
403 toplevel = base_window_get_gtk_toplevel( window );
404
405 dialog = gtk_file_chooser_dialog_new(
406 title,
407 toplevel,
408 GTK_FILE_CHOOSER_ACTION_OPEN,
409 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
410 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
411 NULL
412 );
413
414 if( update_preview_cb ){
415 preview = gtk_image_new();
416 gtk_file_chooser_set_preview_widget( GTK_FILE_CHOOSER( dialog ), preview );
417 g_signal_connect( dialog, "update-preview", update_preview_cb, preview );
418 }
419
420 base_gtk_utils_restore_window_position( window, wsp_name );
421
422 text = gtk_entry_get_text( GTK_ENTRY( entry ));
423
424 if( text && strlen( text )){
425 gtk_file_chooser_set_filename( GTK_FILE_CHOOSER( dialog ), text );
426
427 } else {
428 uri = na_settings_get_string( entry_name, NULL, NULL );
429 if( uri ){
430 gtk_file_chooser_set_current_folder_uri( GTK_FILE_CHOOSER( dialog ), uri );
431 g_free( uri );
432 }
433 }
434
435 if( gtk_dialog_run( GTK_DIALOG( dialog )) == GTK_RESPONSE_ACCEPT ){
436 filename = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER( dialog ));
437 gtk_entry_set_text( GTK_ENTRY( entry ), filename );
438 g_free( filename );
439 }
440
441 uri = gtk_file_chooser_get_current_folder_uri( GTK_FILE_CHOOSER( dialog ));
442 na_settings_set_string( entry_name, uri );
443 g_free( uri );
444
445 base_gtk_utils_save_window_position( window, wsp_name );
446
447 gtk_widget_destroy( dialog );
448 }
449
450 /**
451 * base_gtk_utils_select_dir:
452 * @window: the #BaseWindow which will be the parent of the dialog box.
453 * @title: the title of the dialog box.
454 * @wsp_name: the name of the dialog box in Preferences to read/write
455 * its size and position.
456 * @entry: the #GtkEntry which is associated with the field.
457 * @entry_name: the name of the entry in Preferences to be read/written.
458 * @default_dir_uri: the URI of the directory which should be set in there is
459 * not yet any preference (see @entry_name)
460 *
461 * Opens a #GtkFileChooserDialog and let the user choose an existing directory
462 * -> choose and display an existing dir name
463 * -> record the dirname URI of this dir name.
464 *
465 * If the user validates its selection, the chosen file pathname will be
466 * written in the @entry #GtkEntry, while the corresponding dirname
467 * URI will be written as @entry_name in Preferences.
468 */
469 void
base_gtk_utils_select_dir(BaseWindow * window,const gchar * title,const gchar * wsp_name,GtkWidget * entry,const gchar * entry_name)470 base_gtk_utils_select_dir( BaseWindow *window,
471 const gchar *title, const gchar *wsp_name,
472 GtkWidget *entry, const gchar *entry_name )
473 {
474 GtkWindow *toplevel;
475 GtkWidget *dialog;
476 const gchar *text;
477 gchar *dir, *uri;
478
479 toplevel = base_window_get_gtk_toplevel( window );
480
481 dialog = gtk_file_chooser_dialog_new(
482 title,
483 toplevel,
484 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
485 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
486 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
487 NULL
488 );
489
490 base_gtk_utils_restore_window_position( window, wsp_name );
491
492 text = gtk_entry_get_text( GTK_ENTRY( entry ));
493
494 if( text && strlen( text )){
495 gtk_file_chooser_set_filename( GTK_FILE_CHOOSER( dialog ), text );
496
497 } else {
498 uri = na_settings_get_string( entry_name, NULL, NULL );
499 if( uri ){
500 gtk_file_chooser_set_current_folder_uri( GTK_FILE_CHOOSER( dialog ), uri );
501 g_free( uri );
502 }
503 }
504
505 if( gtk_dialog_run( GTK_DIALOG( dialog )) == GTK_RESPONSE_ACCEPT ){
506 dir = gtk_file_chooser_get_filename( GTK_FILE_CHOOSER( dialog ));
507 gtk_entry_set_text( GTK_ENTRY( entry ), dir );
508 g_free( dir );
509 }
510
511 uri = gtk_file_chooser_get_current_folder_uri( GTK_FILE_CHOOSER( dialog ));
512 na_settings_set_string( entry_name, uri );
513 g_free( uri );
514
515 base_gtk_utils_save_window_position( window, wsp_name );
516
517 gtk_widget_destroy( dialog );
518 }
519
520 /*
521 * base_gtk_utils_table_to_grid:
522 * @window: the #BaseWindow container.
523 * @table_name: the name of the #GtkTable to be replaced.
524 *
525 * Dynamically replaces a GtkTable with a GtkGrid, doing its best in order
526 * to preserve order and name of all children.
527 *
528 * The caller has to take care of calling this function for Gtk 3.x, only
529 * replacing valuable GtkTables.
530 *
531 * This function should be called from on_base_initialize_gtk().
532 */
533 void
base_gtk_utils_table_to_grid(BaseWindow * window,const gchar * table_name)534 base_gtk_utils_table_to_grid( BaseWindow *window, const gchar *table_name )
535 {
536 #if GTK_CHECK_VERSION( 3,0,0 )
537 static const gchar *thisfn = "base_gtk_utils_table_to_grid";
538 TableToGridData ttg;
539 GtkWidget *parent;
540 guint col_spacing, row_spacing;
541
542 memset( &ttg, '\0', sizeof( TableToGridData ));
543
544 ttg.table = na_gtk_utils_find_widget_by_name( GTK_CONTAINER( base_window_get_gtk_toplevel( window )), table_name );
545 g_return_if_fail( ttg.table );
546 g_return_if_fail( GTK_IS_TABLE( ttg.table ));
547 g_debug( "%s: table=%p (%s)", thisfn, ( void * ) ttg.table, gtk_buildable_get_name( GTK_BUILDABLE( ttg.table )));
548
549 parent = gtk_widget_get_parent( ttg.table );
550
551 #ifdef NA_MAINTAINER_MODE
552 na_gtk_utils_dump_children( GTK_CONTAINER( parent ));
553 #endif
554
555 #if !GTK_CHECK_VERSION( 3,4,0 )
556 gtk_table_get_size( GTK_TABLE( ttg.table ), &ttg.rows, &ttg.columns );
557 col_spacing = gtk_table_get_default_col_spacing( GTK_TABLE( ttg.table ));
558 row_spacing = gtk_table_get_default_row_spacing( GTK_TABLE( ttg.table ));
559 #else
560 col_spacing = 6;
561 row_spacing = 6;
562 #endif
563
564 ttg.grid = gtk_grid_new();
565 gtk_grid_set_column_spacing( GTK_GRID( ttg.grid ), col_spacing );
566 gtk_grid_set_row_spacing( GTK_GRID( ttg.grid ), row_spacing );
567
568 gtk_container_foreach( GTK_CONTAINER( ttg.table ), ( GtkCallback ) table_to_grid_foreach_cb, &ttg );
569 /*gtk_widget_unparent( ttg.table );*/
570
571 if( GTK_IS_ALIGNMENT( parent )){
572 gtk_container_remove( GTK_CONTAINER( parent ), ttg.table );
573 gtk_container_add( GTK_CONTAINER( parent ), ttg.grid );
574 } else {
575 g_warning( "%s: untreated parent of class %s", thisfn, G_OBJECT_TYPE_NAME( parent ));
576 }
577
578 #ifdef NA_MAINTAINER_MODE
579 na_gtk_utils_dump_children( GTK_CONTAINER( parent ));
580 #endif
581 #endif
582 }
583
584 #if GTK_CHECK_VERSION( 3,0,0 )
585 static void
table_to_grid_foreach_cb(GtkWidget * widget,TableToGridData * ttg)586 table_to_grid_foreach_cb( GtkWidget *widget, TableToGridData *ttg )
587 {
588 static const gchar *thisfn = "base_gtk_utils_table_to_grid_foreach_cb";
589 guint left, top, x_options;
590
591 g_debug( "%s: widget=%p (%s)", thisfn, ( void * ) widget, gtk_buildable_get_name( GTK_BUILDABLE( widget )));
592
593 gtk_container_child_get( GTK_CONTAINER( ttg->table ), widget,
594 "left-attach", &left, "top-attach", &top, "x-options", &x_options, NULL );
595 gtk_widget_unparent( widget );
596 gtk_grid_attach( GTK_GRID( ttg->grid ), widget, left, top, 1, 1 );
597 gtk_widget_set_hexpand( widget, x_options & GTK_EXPAND );
598 }
599 #endif
600