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