3 * A Nautilus extension which offers configurable context menu actions.
5 * Copyright (C) 2005 The GNOME Foundation
6 * Copyright (C) 2006, 2007, 2008 Frederic Ruaudel and others (see AUTHORS)
7 * Copyright (C) 2009, 2010, 2011 Pierre Wieser and others (see AUTHORS)
9 * This Program 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.
14 * This Program 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
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public
20 * License along with this Library; see the file COPYING. If not,
21 * write to the Free Software Foundation, Inc., 59 Temple Place,
22 * Suite 330, Boston, MA 02111-1307, USA.
25 * Frederic Ruaudel <grumz@grumz.net>
26 * Rodrigo Moya <rodrigo@gnome-db.org>
27 * Pierre Wieser <pwieser@trychlos.org>
28 * ... and many others (see AUTHORS)
38 #include <core/na-iprefs.h>
39 #include <core/na-updater.h>
41 #include "base-iprefs.h"
42 #include "nact-gtk-utils.h"
43 #include "nact-application.h"
45 #define NACT_PROP_TOGGLE_BUTTON "nact-prop-toggle-button"
46 #define NACT_PROP_TOGGLE_HANDLER "nact-prop-toggle-handler"
47 #define NACT_PROP_TOGGLE_USER_DATA "nact-prop-toggle-user-data"
49 #define DEFAULT_WIDTH 22
50 #define DEFAULT_HEIGHT 22
52 static GtkWidget
*search_for_child_widget( GtkContainer
*container
, const gchar
*name
);
55 * nact_gtk_utils_set_editable:
56 * @widget: the #GtkWdiget.
57 * @editable: whether the @widget is editable or not.
59 * Try to set a visual indication of whether the @widget is editable or not.
61 * Having a GtkWidget should be enough, but we also deal with a GtkTreeViewColumn.
62 * So the most-bottom common ancestor is just GObject (since GtkObject having been
63 * deprecated in Gtk+-3.0)
66 nact_gtk_utils_set_editable( GObject
*widget
, gboolean editable
)
68 GList
*renderers
, *irender
;
70 /* GtkComboBoxEntry is deprecated from Gtk+3
71 * see. http://git.gnome.org/browse/gtk+/commit/?id=9612c648176378bf237ad0e1a8c6c995b0ca7c61
72 * while 'has_entry' property exists since 2.24
74 #if GTK_CHECK_VERSION( 2, 24, 0 )
75 if( GTK_IS_COMBO_BOX( widget
) && gtk_combo_box_get_has_entry( GTK_COMBO_BOX( widget
))){
77 if( GTK_IS_COMBO_BOX_ENTRY( widget
)){
79 /* idem as GtkEntry */
80 gtk_editable_set_editable( GTK_EDITABLE( gtk_bin_get_child( GTK_BIN( widget
))), editable
);
81 g_object_set( G_OBJECT( gtk_bin_get_child( GTK_BIN( widget
))), "can-focus", editable
, NULL
);
82 /* disable the listbox button itself */
83 gtk_combo_box_set_button_sensitivity( GTK_COMBO_BOX( widget
), editable
? GTK_SENSITIVITY_ON
: GTK_SENSITIVITY_OFF
);
85 } else if( GTK_IS_COMBO_BOX( widget
)){
86 /* disable the listbox button itself */
87 gtk_combo_box_set_button_sensitivity( GTK_COMBO_BOX( widget
), editable
? GTK_SENSITIVITY_ON
: GTK_SENSITIVITY_OFF
);
89 } else if( GTK_IS_ENTRY( widget
)){
90 gtk_editable_set_editable( GTK_EDITABLE( widget
), editable
);
91 /* removing the frame leads to a disturbing modification of the
92 * height of the control */
93 /*g_object_set( G_OBJECT( widget ), "has-frame", editable, NULL );*/
94 /* this prevents the caret to be displayed when we click in the entry */
95 g_object_set( G_OBJECT( widget
), "can-focus", editable
, NULL
);
97 } else if( GTK_IS_TEXT_VIEW( widget
)){
98 g_object_set( G_OBJECT( widget
), "can-focus", editable
, NULL
);
99 gtk_text_view_set_editable( GTK_TEXT_VIEW( widget
), editable
);
101 } else if( GTK_IS_TOGGLE_BUTTON( widget
)){
102 /* transforms to a quasi standard GtkButton */
103 /*g_object_set( G_OBJECT( widget ), "draw-indicator", editable, NULL );*/
104 /* this at least prevent the keyboard focus to go to the button
105 * (which is better than nothing) */
106 g_object_set( G_OBJECT( widget
), "can-focus", editable
, NULL
);
108 } else if( GTK_IS_TREE_VIEW_COLUMN( widget
)){
109 renderers
= gtk_cell_layout_get_cells( GTK_CELL_LAYOUT( GTK_TREE_VIEW_COLUMN( widget
)));
110 for( irender
= renderers
; irender
; irender
= irender
->next
){
111 if( GTK_IS_CELL_RENDERER_TEXT( irender
->data
)){
112 g_object_set( G_OBJECT( irender
->data
), "editable", editable
, "editable-set", TRUE
, NULL
);
115 g_list_free( renderers
);
117 } else if( GTK_IS_BUTTON( widget
)){
118 gtk_widget_set_sensitive( GTK_WIDGET( widget
), editable
);
123 * nact_gtk_utils_radio_set_initial_state:
124 * @button: the #GtkRadioButton button which is initially active.
125 * @handler: the corresponding "toggled" handler.
126 * @user_data: the user data associated to the handler.
127 * @editable: whether this radio button group is editable.
128 * @sensitive: whether this radio button group is sensitive.
130 * This function should be called for the button which is initially active
131 * inside of a radio button group when the radio group may happen to not be
133 * This function should be called only once for the radio button group.
135 * It does the following operations:
136 * - set the button as active
137 * - set other buttons of the radio button group as inactive
138 * - set all buttons of radio button group as @editable
140 * The initially active @button, along with its @handler, are recorded
141 * as properties of the radio button group (actually as properties of each
142 * radio button of the group), so that they can later be used to reset the
146 nact_gtk_utils_radio_set_initial_state( GtkRadioButton
*button
,
147 GCallback handler
, void *user_data
, gboolean editable
, gboolean sensitive
)
150 GtkRadioButton
*other
;
152 group
= gtk_radio_button_get_group( button
);
154 for( ig
= group
; ig
; ig
= ig
->next
){
155 other
= GTK_RADIO_BUTTON( ig
->data
);
156 g_object_set_data( G_OBJECT( other
), NACT_PROP_TOGGLE_BUTTON
, button
);
157 g_object_set_data( G_OBJECT( other
), NACT_PROP_TOGGLE_HANDLER
, handler
);
158 g_object_set_data( G_OBJECT( other
), NACT_PROP_TOGGLE_USER_DATA
, user_data
);
159 g_object_set_data( G_OBJECT( other
), NACT_PROP_TOGGLE_EDITABLE
, GUINT_TO_POINTER( editable
));
160 nact_gtk_utils_set_editable( G_OBJECT( other
), editable
);
161 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( other
), FALSE
);
162 gtk_widget_set_sensitive( GTK_WIDGET( other
), sensitive
);
165 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button
), TRUE
);
169 * nact_gtk_utils_radio_reset_initial_state:
170 * @button: the #GtkRadioButton being toggled.
171 * @handler: the corresponding "toggled" handler.
172 * @data: data associated with the @handler callback.
174 * When clicking on a read-only radio button, this function ensures that
175 * the radio button is not modified. It may be called whether the radio
176 * button group is editable or not (does nothing if group is actually
180 nact_gtk_utils_radio_reset_initial_state( GtkRadioButton
*button
, GCallback handler
)
182 GtkToggleButton
*initial_button
;
183 GCallback initial_handler
;
188 active
= gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON( button
));
189 editable
= ( gboolean
) GPOINTER_TO_UINT( g_object_get_data( G_OBJECT( button
), NACT_PROP_TOGGLE_EDITABLE
));
191 if( active
&& !editable
){
192 initial_button
= GTK_TOGGLE_BUTTON( g_object_get_data( G_OBJECT( button
), NACT_PROP_TOGGLE_BUTTON
));
193 initial_handler
= G_CALLBACK( g_object_get_data( G_OBJECT( button
), NACT_PROP_TOGGLE_HANDLER
));
194 user_data
= g_object_get_data( G_OBJECT( button
), NACT_PROP_TOGGLE_USER_DATA
);
197 g_signal_handlers_block_by_func(( gpointer
) button
, handler
, user_data
);
199 g_signal_handlers_block_by_func(( gpointer
) initial_button
, initial_handler
, user_data
);
201 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON( button
), FALSE
);
202 gtk_toggle_button_set_active( initial_button
, TRUE
);
204 g_signal_handlers_unblock_by_func(( gpointer
) initial_button
, initial_handler
, user_data
);
206 g_signal_handlers_unblock_by_func(( gpointer
) button
, handler
, user_data
);
212 * nact_gtk_utils_toggle_set_initial_state:
213 * @button: the #GtkToggleButton button.
214 * @handler: the corresponding "toggled" handler.
215 * @window: the toplevel #BaseWindow which embeds the button;
216 * it will be passed as user_data when connecting the signal.
217 * @active: whether the check button is initially active (checked).
218 * @editable: whether this radio button group is editable.
219 * @sensitive: whether this radio button group is sensitive.
221 * This function should be called for a check button which may happen to be
224 * It does the following operations:
225 * - connect the 'toggled' handler to the button
226 * - set the button as active or inactive depending of @active
227 * - set the button as editable or not depending of @editable
228 * - set the button as sensitive or not depending of @sensitive
229 * - explictely triggers the 'toggled' handler
232 nact_gtk_utils_toggle_set_initial_state( BaseWindow
*window
,
233 const gchar
*button_name
, GCallback handler
,
234 gboolean active
, gboolean editable
, gboolean sensitive
)
236 typedef void ( *toggle_handler
)( GtkToggleButton
*, BaseWindow
* );
237 GtkToggleButton
*button
;
239 button
= GTK_TOGGLE_BUTTON( base_window_get_widget( window
, button_name
));
242 base_window_signal_connect( window
, G_OBJECT( button
), "toggled", handler
);
244 g_object_set_data( G_OBJECT( button
), NACT_PROP_TOGGLE_HANDLER
, handler
);
245 g_object_set_data( G_OBJECT( button
), NACT_PROP_TOGGLE_USER_DATA
, window
);
246 g_object_set_data( G_OBJECT( button
), NACT_PROP_TOGGLE_EDITABLE
, GUINT_TO_POINTER( editable
));
248 nact_gtk_utils_set_editable( G_OBJECT( button
), editable
);
249 gtk_widget_set_sensitive( GTK_WIDGET( button
), sensitive
);
250 gtk_toggle_button_set_active( button
, active
);
252 ( *( toggle_handler
) handler
)( button
, window
);
257 * nact_gtk_utils_toggle_reset_initial_state:
258 * @button: the #GtkToggleButton check button.
260 * When clicking on a read-only check button, this function ensures that
261 * the check button is not modified. It may be called whether the button
262 * is editable or not (does nothing if button is actually editable).
265 nact_gtk_utils_toggle_reset_initial_state( GtkToggleButton
*button
)
272 editable
= ( gboolean
) GPOINTER_TO_UINT( g_object_get_data( G_OBJECT( button
), NACT_PROP_TOGGLE_EDITABLE
));
275 active
= gtk_toggle_button_get_active( button
);
276 handler
= G_CALLBACK( g_object_get_data( G_OBJECT( button
), NACT_PROP_TOGGLE_HANDLER
));
277 user_data
= g_object_get_data( G_OBJECT( button
), NACT_PROP_TOGGLE_USER_DATA
);
279 g_signal_handlers_block_by_func(( gpointer
) button
, handler
, user_data
);
280 gtk_toggle_button_set_active( button
, !active
);
281 g_signal_handlers_unblock_by_func(( gpointer
) button
, handler
, user_data
);
286 * nact_utils_get_pixbuf:
287 * @name: the name of the file or an icon.
288 * widget: the widget on which the imagecshould be rendered.
289 * size: the desired size.
291 * Returns a pixbuf for the given widget.
294 nact_gtk_utils_get_pixbuf( const gchar
*name
, GtkWidget
*widget
, GtkIconSize size
)
296 static const gchar
*thisfn
= "nact_gtk_utils_get_pixbuf";
300 GtkIconTheme
*icon_theme
;
305 if( !gtk_icon_size_lookup( size
, &width
, &height
)){
306 width
= DEFAULT_WIDTH
;
307 height
= DEFAULT_HEIGHT
;
310 if( name
&& strlen( name
)){
311 if( g_path_is_absolute( name
)){
312 pixbuf
= gdk_pixbuf_new_from_file_at_size( name
, width
, height
, &error
);
314 if( error
->code
!= G_FILE_ERROR_NOENT
){
315 g_warning( "%s: gdk_pixbuf_new_from_file_at_size: name=%s, error=%s (%d)", thisfn
, name
, error
->message
, error
->code
);
317 g_error_free( error
);
323 /* gtk_widget_render_icon() is deprecated since Gtk+ 3.0
324 * see http://library.gnome.org/devel/gtk/unstable/GtkWidget.html#gtk-widget-render-icon
325 * and http://git.gnome.org/browse/gtk+/commit/?id=07eeae15825403037b7df139acf9bfa104d5559d
327 #if GTK_CHECK_VERSION( 2, 91, 7 )
328 pixbuf
= gtk_widget_render_icon_pixbuf( widget
, name
, size
);
330 pixbuf
= gtk_widget_render_icon( widget
, name
, size
, NULL
);
333 icon_theme
= gtk_icon_theme_get_default();
334 pixbuf
= gtk_icon_theme_load_icon(
335 icon_theme
, name
, width
, GTK_ICON_LOOKUP_GENERIC_FALLBACK
, &error
);
337 /* it happens that the message "Icon 'xxxx' not present in theme"
338 * is generated with a domain of 'gtk-icon-theme-error-quark' and
339 * an error code of zero - it seems difficult to just test zero
340 * so does not display warning, but just debug
342 g_debug( "%s: %s (%s:%d)",
343 thisfn
, error
->message
, g_quark_to_string( error
->domain
), error
->code
);
344 g_error_free( error
);
351 g_debug( "%s: null pixbuf, loading transparent image", thisfn
);
352 pixbuf
= gdk_pixbuf_new_from_file_at_size( PKGDATADIR
"/transparent.png", width
, height
, NULL
);
360 * @name: the name of the file or an icon, or %NULL.
361 * widget: the widget on which the image should be rendered.
362 * size: the desired size.
364 * Displays the (maybe themed) image on the given widget.
367 nact_gtk_utils_render( const gchar
*name
, GtkImage
*widget
, GtkIconSize size
)
369 static const gchar
*thisfn
= "nact_gtk_utils_render";
373 g_debug( "%s: name=%s, widget=%p, size=%d", thisfn
, name
, ( void * ) widget
, size
);
376 pixbuf
= nact_gtk_utils_get_pixbuf( name
, GTK_WIDGET( widget
), size
);
379 if( !gtk_icon_size_lookup( size
, &width
, &height
)){
380 width
= DEFAULT_WIDTH
;
381 height
= DEFAULT_HEIGHT
;
383 pixbuf
= gdk_pixbuf_new_from_file_at_size( PKGDATADIR
"/transparent.png", width
, height
, NULL
);
387 gtk_image_set_from_pixbuf( widget
, pixbuf
);
388 g_object_unref( pixbuf
);
393 * nact_gtk_utils_select_file:
394 * @window: the #BaseWindow which will be the parent of the dialog box.
395 * @title: the title of the dialog box.
396 * @dialog_name: the name of the dialog box in Preferences to read/write
397 * its size and position.
398 * @entry: the #GtkEntry which is associated with the selected file.
399 * @entry_name: the name of the entry in Preferences to be read/written.
401 * Opens a #GtkFileChooserDialog and let the user choose an existing file
402 * -> choose and display an existing file name
403 * -> record the dirname URI.
405 * If the user validates its selection, the choosen file pathname will be
406 * written in the @entry #GtkEntry, while the corresponding dirname
407 * URI will be written as @entry_name in Preferences.
410 nact_gtk_utils_select_file( BaseWindow
*window
,
411 const gchar
*title
, const gchar
*dialog_name
,
412 GtkWidget
*entry
, const gchar
*entry_name
)
414 nact_gtk_utils_select_file_with_preview(
415 window
, title
, dialog_name
, entry
, entry_name
, NULL
);
419 * nact_gtk_utils_select_file_with_preview:
420 * @window: the #BaseWindow which will be the parent of the dialog box.
421 * @title: the title of the dialog box.
422 * @dialog_name: the name of the dialog box in Preferences to read/write
423 * its size and position.
424 * @entry: the #GtkEntry which is associated with the selected file.
425 * @entry_name: the name of the entry in Preferences to be read/written.
426 * @update_preview_cb: the callback function in charge of updating the
427 * preview widget. May be NULL.
429 * Opens a #GtkFileChooserDialog and let the user choose an existing file
430 * -> choose and display an existing file name
431 * -> record the dirname URI.
433 * If the user validates its selection, the choosen file pathname will be
434 * written in the @entry #GtkEntry, while the corresponding dirname
435 * URI will be written as @entry_name in Preferences.
438 nact_gtk_utils_select_file_with_preview( BaseWindow
*window
,
439 const gchar
*title
, const gchar
*dialog_name
,
440 GtkWidget
*entry
, const gchar
*entry_name
,
441 GCallback update_preview_cb
)
443 NactApplication
*application
;
448 gchar
*filename
, *uri
;
450 NASettings
*settings
;
452 application
= NACT_APPLICATION( base_window_get_application( window
));
453 updater
= nact_application_get_updater( application
);
454 settings
= na_pivot_get_settings( NA_PIVOT( updater
));
455 toplevel
= base_window_get_gtk_toplevel( window
);
457 dialog
= gtk_file_chooser_dialog_new(
460 GTK_FILE_CHOOSER_ACTION_OPEN
,
461 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
462 GTK_STOCK_OPEN
, GTK_RESPONSE_ACCEPT
,
466 if( update_preview_cb
){
467 preview
= gtk_image_new();
468 gtk_file_chooser_set_preview_widget( GTK_FILE_CHOOSER( dialog
), preview
);
469 g_signal_connect( dialog
, "update-preview", update_preview_cb
, preview
);
472 base_iprefs_position_named_window( window
, GTK_WINDOW( dialog
), dialog_name
);
474 text
= gtk_entry_get_text( GTK_ENTRY( entry
));
476 if( text
&& strlen( text
)){
477 gtk_file_chooser_set_filename( GTK_FILE_CHOOSER( dialog
), text
);
480 uri
= na_settings_get_string( settings
, entry_name
, NULL
, NULL
);
482 gtk_file_chooser_set_current_folder_uri( GTK_FILE_CHOOSER( dialog
), uri
);
487 if( gtk_dialog_run( GTK_DIALOG( dialog
)) == GTK_RESPONSE_ACCEPT
){
488 filename
= gtk_file_chooser_get_filename( GTK_FILE_CHOOSER( dialog
));
489 gtk_entry_set_text( GTK_ENTRY( entry
), filename
);
493 uri
= gtk_file_chooser_get_current_folder_uri( GTK_FILE_CHOOSER( dialog
));
494 na_settings_set_string( settings
, entry_name
, uri
);
497 base_iprefs_save_named_window_position( window
, GTK_WINDOW( dialog
), dialog_name
);
499 gtk_widget_destroy( dialog
);
503 * nact_gtk_utils_select_dir:
504 * @window: the #BaseWindow which will be the parent of the dialog box.
505 * @title: the title of the dialog box.
506 * @dialog_name: the name of the dialog box in Preferences to read/write
507 * its size and position.
508 * @entry: the #GtkEntry which is associated with the field.
509 * @entry_name: the name of the entry in Preferences to be read/written.
510 * @default_dir_uri: the URI of the directory which should be set in there is
511 * not yet any preference (see @entry_name)
513 * Opens a #GtkFileChooserDialog and let the user choose an existing directory
514 * -> choose and display an existing dir name
515 * -> record the dirname URI of this dir name.
517 * If the user validates its selection, the choosen file pathname will be
518 * written in the @entry #GtkEntry, while the corresponding dirname
519 * URI will be written as @entry_name in Preferences.
522 nact_gtk_utils_select_dir( BaseWindow
*window
,
523 const gchar
*title
, const gchar
*dialog_name
,
524 GtkWidget
*entry
, const gchar
*entry_name
)
526 NactApplication
*application
;
532 NASettings
*settings
;
534 application
= NACT_APPLICATION( base_window_get_application( window
));
535 updater
= nact_application_get_updater( application
);
536 settings
= na_pivot_get_settings( NA_PIVOT( updater
));
537 toplevel
= base_window_get_gtk_toplevel( window
);
539 dialog
= gtk_file_chooser_dialog_new(
542 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
,
543 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
544 GTK_STOCK_OPEN
, GTK_RESPONSE_ACCEPT
,
548 base_iprefs_position_named_window( window
, GTK_WINDOW( dialog
), dialog_name
);
550 text
= gtk_entry_get_text( GTK_ENTRY( entry
));
552 if( text
&& strlen( text
)){
553 gtk_file_chooser_set_filename( GTK_FILE_CHOOSER( dialog
), text
);
556 uri
= na_settings_get_string( settings
, entry_name
, NULL
, NULL
);
558 gtk_file_chooser_set_current_folder_uri( GTK_FILE_CHOOSER( dialog
), uri
);
563 if( gtk_dialog_run( GTK_DIALOG( dialog
)) == GTK_RESPONSE_ACCEPT
){
564 dir
= gtk_file_chooser_get_filename( GTK_FILE_CHOOSER( dialog
));
565 gtk_entry_set_text( GTK_ENTRY( entry
), dir
);
569 uri
= gtk_file_chooser_get_current_folder_uri( GTK_FILE_CHOOSER( dialog
));
570 na_settings_set_string( settings
, entry_name
, uri
);
573 base_iprefs_save_named_window_position( window
, GTK_WINDOW( dialog
), dialog_name
);
575 gtk_widget_destroy( dialog
);
579 * nact_gtk_utils_get_widget_by_name:
580 * @toplevel: the #GtkWindow toplevel.
581 * @name: the name of the searched child.
583 * Returns: a pointer to the named widget which is a child of @toplevel,
584 * or %NULL. the returned pointer is owned by #GtkBuilder instance, and
585 * should not be released by the caller.
588 nact_gtk_utils_get_widget_by_name( GtkWindow
*toplevel
, const gchar
*name
)
590 static const gchar
*thisfn
= "nact_gtk_utils_get_widget_by_name";
591 GtkWidget
*widget
= NULL
;
593 g_return_val_if_fail( GTK_IS_WINDOW( toplevel
), NULL
);
595 widget
= search_for_child_widget( GTK_CONTAINER( toplevel
), name
);
598 g_warning( "%s: widget not found: %s", thisfn
, name
);
601 g_return_val_if_fail( GTK_IS_WIDGET( widget
), NULL
);
608 search_for_child_widget( GtkContainer
*container
, const gchar
*name
)
610 GList
*children
= gtk_container_get_children( container
);
612 GtkWidget
*found
= NULL
;
614 const gchar
*child_name
;
616 for( ic
= children
; ic
; ic
= ic
->next
){
617 if( GTK_IS_WIDGET( ic
->data
)){
618 child
= GTK_WIDGET( ic
->data
);
619 child_name
= gtk_buildable_get_name( GTK_BUILDABLE( child
));
620 if( child_name
&& strlen( child_name
)){
621 /*g_debug( "%s: child=%s", thisfn, child_name );*/
622 if( !g_ascii_strcasecmp( name
, child_name
)){
626 } else if( GTK_IS_CONTAINER( child
)){
627 found
= search_for_child_widget( GTK_CONTAINER( child
), name
);
636 g_list_free( children
);