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