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 <glib.h>
35 #include <string.h>
36 
37 #include "na-gtk-utils.h"
38 #include "na-settings.h"
39 
40 static void   int_list_to_position( GList *list, gint *x, gint *y, gint *width, gint *height );
41 static GList *position_to_int_list( gint x, gint y, gint width, gint height );
42 static void   free_int_list( GList *list );
43 
44 /*
45  * na_gtk_utils_find_widget_by_name:
46  * @container: a #GtkContainer, usually the #GtkWindow toplevel.
47  * @name: the name of the searched widget.
48  *
49  * Returns: the searched widget.
50  */
51 GtkWidget *
na_gtk_utils_find_widget_by_name(GtkContainer * container,const gchar * name)52 na_gtk_utils_find_widget_by_name( GtkContainer *container, const gchar *name )
53 {
54 	GList *children = gtk_container_get_children( container );
55 	GList *ic;
56 	GtkWidget *found = NULL;
57 	GtkWidget *child;
58 	const gchar *child_name;
59 
60 	for( ic = children ; ic && !found ; ic = ic->next ){
61 
62 		if( GTK_IS_WIDGET( ic->data )){
63 			child = GTK_WIDGET( ic->data );
64 			child_name = gtk_buildable_get_name( GTK_BUILDABLE( child ));
65 			if( child_name && strlen( child_name ) && !g_ascii_strcasecmp( name, child_name )){
66 				found = child;
67 				break;
68 			}
69 			if( GTK_IS_CONTAINER( child )){
70 				found = na_gtk_utils_find_widget_by_name( GTK_CONTAINER( child ), name );
71 			}
72 		}
73 	}
74 
75 	g_list_free( children );
76 	return( found );
77 }
78 
79 #ifdef NA_MAINTAINER_MODE
80 static void
dump_children(const gchar * thisfn,GtkContainer * container,int level)81 dump_children( const gchar *thisfn, GtkContainer *container, int level )
82 {
83 	GList *children = gtk_container_get_children( container );
84 	GList *ic;
85 	GtkWidget *child;
86 	const gchar *child_name;
87 	GString *prefix;
88 	int i;
89 
90 	prefix = g_string_new( "" );
91 	for( i = 0 ; i <= level ; ++i ){
92 		g_string_append_printf( prefix, "%s", "|  " );
93 	}
94 
95 	for( ic = children ; ic ; ic = ic->next ){
96 
97 		if( GTK_IS_WIDGET( ic->data )){
98 			child = GTK_WIDGET( ic->data );
99 			child_name = gtk_buildable_get_name( GTK_BUILDABLE( child ));
100 			g_debug( "%s: %s%s\t%p %s",
101 					thisfn, prefix->str, G_OBJECT_TYPE_NAME( child ), ( void * ) child, child_name );
102 
103 			if( GTK_IS_CONTAINER( child )){
104 				dump_children( thisfn, GTK_CONTAINER( child ), level+1 );
105 			}
106 		}
107 	}
108 
109 	g_list_free( children );
110 	g_string_free( prefix, TRUE );
111 }
112 
113 void
na_gtk_utils_dump_children(GtkContainer * container)114 na_gtk_utils_dump_children( GtkContainer *container )
115 {
116 	static const gchar *thisfn = "na_gtk_utils_dump_children";
117 
118 	g_debug( "%s: container=%p", thisfn, container );
119 
120 	dump_children( thisfn, container, 0 );
121 }
122 #endif
123 
124 /**
125  * na_gtk_utils_restore_position_window:
126  * @toplevel: the #GtkWindow window.
127  * @wsp_name: the string which handles the window size and position in user preferences.
128  *
129  * Position the specified window on the screen.
130  *
131  * A window position is stored as a list of integers "x,y,width,height".
132  */
133 void
na_gtk_utils_restore_window_position(GtkWindow * toplevel,const gchar * wsp_name)134 na_gtk_utils_restore_window_position( GtkWindow *toplevel, const gchar *wsp_name )
135 {
136 	static const gchar *thisfn = "na_gtk_utils_restore_window_position";
137 	GList *list;
138 	gint x=0, y=0, width=0, height=0;
139 	GdkDisplay *display;
140 	GdkScreen *screen;
141 	gint screen_width, screen_height;
142 
143 	g_return_if_fail( GTK_IS_WINDOW( toplevel ));
144 	g_return_if_fail( wsp_name && strlen( wsp_name ));
145 
146 	g_debug( "%s: toplevel=%p (%s), wsp_name=%s",
147 			thisfn, ( void * ) toplevel, G_OBJECT_TYPE_NAME( toplevel ), wsp_name );
148 
149 	list = na_settings_get_uint_list( wsp_name, NULL, NULL );
150 
151 	if( list ){
152 		int_list_to_position( list, &x, &y, &width, &height );
153 		g_debug( "%s: wsp_name=%s, x=%d, y=%d, width=%d, height=%d", thisfn, wsp_name, x, y, width, height );
154 		free_int_list( list );
155 	}
156 
157 	x = MAX( 1, x );
158 	y = MAX( 1, y );
159 	width = MAX( 1, width );
160 	height = MAX( 1, height );
161 
162 	/* bad hack for the first time we open the main window
163 	 * try to target an ideal size and position
164 	 */
165 	if( !strcmp( wsp_name, NA_IPREFS_MAIN_WINDOW_WSP )){
166 		if( x == 1 && y == 1 && width == 1 && height == 1 ){
167 			x = 50;
168 			y = 70;
169 			width = 1030;
170 			height = 560;
171 
172 		} else {
173 			display = gdk_display_get_default();
174 			screen = gdk_display_get_screen( display, 0 );
175 			screen_width = gdk_screen_get_width( screen );
176 			screen_height = gdk_screen_get_height( screen );
177 			g_debug( "%s: screen=(%d,%d), DEFAULT_HEIGHT=%d",
178 					thisfn, screen_width, screen_height, DEFAULT_HEIGHT );
179 
180 			screen_height -= 2*DEFAULT_HEIGHT;
181 			width = MIN( width, screen_width-x );
182 			height = MIN( height, screen_height-y );
183 		}
184 	}
185 
186 	g_debug( "%s: wsp_name=%s, x=%d, y=%d, width=%d, height=%d",
187 			thisfn, wsp_name, x, y, width, height );
188 
189 	gtk_window_move( toplevel, x, y );
190 	gtk_window_resize( toplevel, width, height );
191 }
192 
193 /**
194  * na_gtk_utils_save_window_position:
195  * @toplevel: the #GtkWindow window.
196  * @wsp_name: the string which handles the window size and position in user preferences.
197  *
198  * Save the size and position of the specified window.
199  */
200 void
na_gtk_utils_save_window_position(GtkWindow * toplevel,const gchar * wsp_name)201 na_gtk_utils_save_window_position( GtkWindow *toplevel, const gchar *wsp_name )
202 {
203 	static const gchar *thisfn = "na_gtk_utils_save_window_position";
204 	gint x, y, width, height;
205 	GList *list;
206 
207 	g_return_if_fail( GTK_IS_WINDOW( toplevel ));
208 	g_return_if_fail( wsp_name && strlen( wsp_name ));
209 
210 	gtk_window_get_position( toplevel, &x, &y );
211 	gtk_window_get_size( toplevel, &width, &height );
212 	g_debug( "%s: wsp_name=%s, x=%d, y=%d, width=%d, height=%d", thisfn, wsp_name, x, y, width, height );
213 
214 	list = position_to_int_list( x, y, width, height );
215 	na_settings_set_uint_list( wsp_name, list );
216 	free_int_list( list );
217 }
218 
219 /*
220  * extract the position of the window from the list of unsigned integers
221  */
222 static void
int_list_to_position(GList * list,gint * x,gint * y,gint * width,gint * height)223 int_list_to_position( GList *list, gint *x, gint *y, gint *width, gint *height )
224 {
225 	GList *it;
226 	int i;
227 
228 	g_assert( x );
229 	g_assert( y );
230 	g_assert( width );
231 	g_assert( height );
232 
233 	for( it=list, i=0 ; it ; it=it->next, i+=1 ){
234 		switch( i ){
235 			case 0:
236 				*x = GPOINTER_TO_UINT( it->data );
237 				break;
238 			case 1:
239 				*y = GPOINTER_TO_UINT( it->data );
240 				break;
241 			case 2:
242 				*width = GPOINTER_TO_UINT( it->data );
243 				break;
244 			case 3:
245 				*height = GPOINTER_TO_UINT( it->data );
246 				break;
247 		}
248 	}
249 }
250 
251 static GList *
position_to_int_list(gint x,gint y,gint width,gint height)252 position_to_int_list( gint x, gint y, gint width, gint height )
253 {
254 	GList *list = NULL;
255 
256 	list = g_list_append( list, GUINT_TO_POINTER( x ));
257 	list = g_list_append( list, GUINT_TO_POINTER( y ));
258 	list = g_list_append( list, GUINT_TO_POINTER( width ));
259 	list = g_list_append( list, GUINT_TO_POINTER( height ));
260 
261 	return( list );
262 }
263 
264 /*
265  * free the list of int
266  */
267 static void
free_int_list(GList * list)268 free_int_list( GList *list )
269 {
270 	g_list_free( list );
271 }
272 
273 /**
274  * na_gtk_utils_set_editable:
275  * @widget: the #GtkWdiget.
276  * @editable: whether the @widget is editable or not.
277  *
278  * Try to set a visual indication of whether the @widget is editable or not.
279  *
280  * Having a GtkWidget should be enough, but we also deal with a GtkTreeViewColumn.
281  * So the most-bottom common ancestor is just GObject (since GtkObject having been
282  * deprecated in Gtk+-3.0)
283  *
284  * Note that using 'sensitivity' property is just a work-around because the
285  * two things have distinct semantics:
286  * - editable: whether we are allowed to modify the value (is not read-only)
287  * - sensitive: whether the value is relevant (has a sense in this context)
288  */
289 void
na_gtk_utils_set_editable(GObject * widget,gboolean editable)290 na_gtk_utils_set_editable( GObject *widget, gboolean editable )
291 {
292 	GList *renderers, *irender;
293 
294 /* GtkComboBoxEntry is deprecated from Gtk+3
295  * see. http://git.gnome.org/browse/gtk+/commit/?id=9612c648176378bf237ad0e1a8c6c995b0ca7c61
296  * while 'has_entry' property exists since 2.24
297  */
298 #if GTK_CHECK_VERSION( 2,24,0 )
299 	if( GTK_IS_COMBO_BOX( widget ) && gtk_combo_box_get_has_entry( GTK_COMBO_BOX( widget ))){
300 #else
301 	if( GTK_IS_COMBO_BOX_ENTRY( widget )){
302 #endif
303 		/* idem as GtkEntry */
304 		gtk_editable_set_editable( GTK_EDITABLE( gtk_bin_get_child( GTK_BIN( widget ))), editable );
305 		g_object_set( G_OBJECT( gtk_bin_get_child( GTK_BIN( widget ))), "can-focus", editable, NULL );
306 		/* disable the listbox button itself */
307 		gtk_combo_box_set_button_sensitivity( GTK_COMBO_BOX( widget ), editable ? GTK_SENSITIVITY_ON : GTK_SENSITIVITY_OFF );
308 
309 	} else if( GTK_IS_COMBO_BOX( widget )){
310 		/* disable the listbox button itself */
311 		gtk_combo_box_set_button_sensitivity( GTK_COMBO_BOX( widget ), editable ? GTK_SENSITIVITY_ON : GTK_SENSITIVITY_OFF );
312 
313 	} else if( GTK_IS_ENTRY( widget )){
314 		gtk_editable_set_editable( GTK_EDITABLE( widget ), editable );
315 		/* removing the frame leads to a disturbing modification of the
316 		 * height of the control */
317 		/*g_object_set( G_OBJECT( widget ), "has-frame", editable, NULL );*/
318 		/* this prevents the caret to be displayed when we click in the entry */
319 		g_object_set( G_OBJECT( widget ), "can-focus", editable, NULL );
320 
321 	} else if( GTK_IS_TEXT_VIEW( widget )){
322 		g_object_set( G_OBJECT( widget ), "can-focus", editable, NULL );
323 		gtk_text_view_set_editable( GTK_TEXT_VIEW( widget ), editable );
324 
325 	} else if( GTK_IS_TOGGLE_BUTTON( widget )){
326 		/* transforms to a quasi standard GtkButton */
327 		/*g_object_set( G_OBJECT( widget ), "draw-indicator", editable, NULL );*/
328 		/* this at least prevent the keyboard focus to go to the button
329 		 * (which is better than nothing) */
330 		g_object_set( G_OBJECT( widget ), "can-focus", editable, NULL );
331 
332 	} else if( GTK_IS_TREE_VIEW_COLUMN( widget )){
333 		renderers = gtk_cell_layout_get_cells( GTK_CELL_LAYOUT( GTK_TREE_VIEW_COLUMN( widget )));
334 		for( irender = renderers ; irender ; irender = irender->next ){
335 			if( GTK_IS_CELL_RENDERER_TEXT( irender->data )){
336 				g_object_set( G_OBJECT( irender->data ), "editable", editable, "editable-set", TRUE, NULL );
337 			}
338 		}
339 		g_list_free( renderers );
340 
341 	} else if( GTK_IS_BUTTON( widget )){
342 		gtk_widget_set_sensitive( GTK_WIDGET( widget ), editable );
343 	}
344 }
345 
346 /**
347  * na_gtk_utils_radio_set_initial_state:
348  * @button: the #GtkRadioButton button which is initially active.
349  * @handler: the corresponding "toggled" handler.
350  * @user_data: the user data associated to the handler.
351  * @editable: whether this radio button group is editable.
352  * @sensitive: whether this radio button group is sensitive.
353  *
354  * This function should be called for the button which is initially active
355  * inside of a radio button group when the radio group may happen to not be
356  * editable.
357  * This function should be called only once for the radio button group.
358  *
359  * It does the following operations:
360  * - set the button as active
361  * - set other buttons of the radio button group as inactive
362  * - set all buttons of radio button group as @editable
363  *
364  * The initially active @button, along with its @handler, are recorded
365  * as properties of the radio button group (actually as properties of each
366  * radio button of the group), so that they can later be used to reset the
367  * initial state.
368  */
369 void
370 na_gtk_utils_radio_set_initial_state( GtkRadioButton *button,
371 		GCallback handler, void *user_data, gboolean editable, gboolean sensitive )
372 {
373 	GSList *group, *ig;
374 	GtkRadioButton *other;
375 
376 	group = gtk_radio_button_get_group( button );
377 
378 	for( ig = group ; ig ; ig = ig->next ){
379 		other = GTK_RADIO_BUTTON( ig->data );
380 		g_object_set_data( G_OBJECT( other ), NA_TOGGLE_DATA_BUTTON, button );
381 		g_object_set_data( G_OBJECT( other ), NA_TOGGLE_DATA_HANDLER, handler );
382 		g_object_set_data( G_OBJECT( other ), NA_TOGGLE_DATA_USER_DATA, user_data );
383 		g_object_set_data( G_OBJECT( other ), NA_TOGGLE_DATA_EDITABLE, GUINT_TO_POINTER( editable ));
384 		na_gtk_utils_set_editable( G_OBJECT( other ), editable );
385 		gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( other ), FALSE );
386 		gtk_widget_set_sensitive( GTK_WIDGET( other ), sensitive );
387 	}
388 
389 	gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button ), TRUE );
390 }
391 
392 /**
393  * na_gtk_utils_radio_reset_initial_state:
394  * @button: the #GtkRadioButton being toggled.
395  * @handler: the corresponding "toggled" handler.
396  * @data: data associated with the @handler callback.
397  *
398  * When clicking on a read-only radio button, this function ensures that
399  * the radio button is not modified. It may be called whether the radio
400  * button group is editable or not (does nothing if group is actually
401  * editable).
402  */
403 void
404 na_gtk_utils_radio_reset_initial_state( GtkRadioButton *button, GCallback handler )
405 {
406 	GtkToggleButton *initial_button;
407 	GCallback initial_handler;
408 	gboolean active;
409 	gboolean editable;
410 	gpointer user_data;
411 
412 	active = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( button ));
413 	editable = ( gboolean ) GPOINTER_TO_UINT( g_object_get_data( G_OBJECT( button ), NA_TOGGLE_DATA_EDITABLE ));
414 
415 	if( active && !editable ){
416 		initial_button = GTK_TOGGLE_BUTTON( g_object_get_data( G_OBJECT( button ), NA_TOGGLE_DATA_BUTTON ));
417 		initial_handler = G_CALLBACK( g_object_get_data( G_OBJECT( button ), NA_TOGGLE_DATA_HANDLER ));
418 		user_data = g_object_get_data( G_OBJECT( button ), NA_TOGGLE_DATA_USER_DATA );
419 
420 		if( handler ){
421 			g_signal_handlers_block_by_func(( gpointer ) button, handler, user_data );
422 		}
423 		g_signal_handlers_block_by_func(( gpointer ) initial_button, initial_handler, user_data );
424 
425 		gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button ), FALSE );
426 		gtk_toggle_button_set_active( initial_button, TRUE );
427 
428 		g_signal_handlers_unblock_by_func(( gpointer ) initial_button, initial_handler, user_data );
429 		if( handler ){
430 			g_signal_handlers_unblock_by_func(( gpointer ) button, handler, user_data );
431 		}
432 	}
433 }
434