/* Screem: screem-file-browser.c * * a file browser widget, which follows the nautilus theme * * Copyright (C) 2002 David A Knight * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * * For contact information with the author of this source code please see * the AUTHORS file. If there is no AUTHORS file present then check the * about box under the help menu for a contact address */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "screem-file-browser.h" #include "screemmarshal.h" #include "support.h" enum { ADDED = 0, REMOVED, ICON_CHANGE, LAST_SIGNAL }; static guint screem_file_browser_signals[ LAST_SIGNAL ] = { 0 }; /* cache mime type strings */ static GStringChunk *mime_cache = NULL; typedef enum { BROWSER_NODE_DIRECTORY, BROWSER_NODE_FILE } BrowserNodeType; typedef struct { BrowserNodeType type; ScreemFileBrowser *browser; gchar *pathname; gchar *basename; const gchar *mime_type; GnomeVFSMonitorHandle *monitor; } BrowserNode; struct ScreemFileBrowserDetails { GtkTreeStore *model; GtkTreeModel *filtered; GtkTreeModel *sorted; int mode; ScreemIconCache *cache; gboolean show_hidden; gboolean show_backups; gchar *backup_suffix; }; static BrowserNode *browser_node_new( ScreemFileBrowser *browser, gint depth, BrowserNodeType type, const gchar *uri, const gchar *mime_type ); static void browser_node_free( BrowserNode *node ); static void scan_directory( ScreemFileBrowser *browser, GtkTreeIter *parent, const gchar *uri, const gchar *mime_type, guint depth, int mode ); static void directory_monitor_cb( GnomeVFSMonitorHandle *handle, const gchar *monitor_uri, const gchar *info_uri, GnomeVFSMonitorEventType type, gpointer data ); static gboolean find_iter( GtkTreeModel *model, const gchar *uri, GtkTreeIter *parent, GtkTreeIter *it ); static void screem_file_browser_set_icons( ScreemFileBrowser *browser, GtkTreeIter *it ); static void screem_file_browser_theme_change( ScreemIconCache *cache, gpointer data ); static gint screem_file_browser_compare_func( GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data ); static GList *screem_file_browser_recurse( const gchar *pathname ); static gboolean screem_file_browser_clear( GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *it, gpointer data ); static gboolean screem_file_browser_filter( GtkTreeModel *model, GtkTreeIter *it, ScreemFileBrowser *browser ); static gboolean refilter( GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, ScreemFileBrowser *browser ); ScreemFileBrowser *screem_file_browser_new( ScreemIconCache *cache ) { ScreemFileBrowser *browser; browser = SCREEM_FILE_BROWSER( g_object_new( screem_file_browser_get_type(), NULL ) ); g_object_ref( cache ); browser->details->cache = cache; g_signal_connect( G_OBJECT( cache ), "changed", G_CALLBACK( screem_file_browser_theme_change ), browser ); return browser; } void screem_file_browser_set_mode( ScreemFileBrowser *browser, ScreemFileBrowserMode mode ) { g_return_if_fail( SCREEM_IS_FILE_BROWSER( browser ) ); browser->details->mode = mode; } GtkTreeModel *screem_file_browser_get_model( ScreemFileBrowser *browser ) { g_return_val_if_fail( SCREEM_IS_FILE_BROWSER( browser ), NULL ); return GTK_TREE_MODEL( browser->details->sorted ); } void screem_file_browser_scan_directory( ScreemFileBrowser *browser, const gchar *uri, int mode ) { GtkTreeIter *parent; GtkTreeIter pit; GtkTreeIter it; GtkTreeModel *model; g_return_if_fail( SCREEM_IS_FILE_BROWSER( browser ) ); g_return_if_fail( uri != NULL ); model = GTK_TREE_MODEL( browser->details->model ); /* try and find uri in the current model, and set parent to the iter's parent if found */ parent = NULL; /* if uri at depth 0 is the root of our tree then * clear all nodes */ if( ( mode & FILE_BROWSE_ROOT ) ) { parent = NULL; gtk_tree_model_foreach( model, screem_file_browser_clear, browser ); gtk_tree_store_clear( GTK_TREE_STORE( model ) ); } else if( find_iter( model, uri, NULL, &it ) ) { if( gtk_tree_model_iter_parent( model, &pit, &it ) ) { parent = &pit; } } if( mode == -1 ) { mode = browser->details->mode; } scan_directory( browser, parent, uri, "x-directory/normal", 0, mode ); if( mode & FILE_BROWSE_ROOT ) { /* replace first iter name with / */ GtkTreeIter fit; if( gtk_tree_model_get_iter_first( model, &fit ) ) { gtk_tree_store_set( GTK_TREE_STORE( model ), &fit, FILE_BROWSER_NAME_COL, "/", -1 ); } } } void screem_file_browser_scan_iter( ScreemFileBrowser *browser, GtkTreeIter *iter, int mode ) { ScreemFileBrowserDetails *details; GtkTreeModel *model; BrowserNode *node; gchar *uri; GtkTreeIter filtered_it; GtkTreeIter realit; GtkTreeIter cit; g_return_if_fail( SCREEM_IS_FILE_BROWSER( browser ) ); g_return_if_fail( iter != NULL ); details = browser->details; model = GTK_TREE_MODEL( details->model ); g_assert( gtk_tree_model_sort_iter_is_valid( GTK_TREE_MODEL_SORT( details->sorted ), iter ) ); /* convert iter to a child iter */ gtk_tree_model_sort_convert_iter_to_child_iter( GTK_TREE_MODEL_SORT( details->sorted ), &filtered_it, iter ); gtk_tree_model_filter_convert_iter_to_child_iter( GTK_TREE_MODEL_FILTER( details->filtered ), &realit, &filtered_it ); iter = &realit; gtk_tree_model_get( model, iter, FILE_BROWSER_NODE_COL, &node, -1 ); if( node && node->type == BROWSER_NODE_DIRECTORY ) { uri = g_strdup( node->pathname ); if( mode == -1 ) { mode = details->mode; } /* remove BrowserNode objects from current children, * this marks them for removeal */ if( gtk_tree_model_iter_children( model, &cit, iter ) ) { do { gtk_tree_model_get( model, &cit, FILE_BROWSER_NODE_COL, &node, -1 ); if( node ) { browser_node_free( node ); gtk_tree_store_set( GTK_TREE_STORE( model ), &cit, FILE_BROWSER_NODE_COL, NULL, -1 ); } } while( gtk_tree_model_iter_next( model, &cit ) ); } scan_directory( browser, iter, uri, "x-directory/normal", 0, mode ); /* now new children are added, remove all children * that have no BrowserNode object */ if( gtk_tree_model_iter_children( model, &cit, iter ) ) { gboolean stop = FALSE; do { GtkTreeIter next; gtk_tree_model_get( model, &cit, FILE_BROWSER_NODE_COL, &node, -1 ); next = cit; stop = ! gtk_tree_model_iter_next( model, &next ); if( ! node ) { gtk_tree_store_remove( GTK_TREE_STORE( model ), &cit ); } cit = next; } while( ! stop ); } g_free( uri ); } } GList *screem_file_browser_get_pathnames( ScreemFileBrowser *browser, GList *iters, gboolean recurse ) { GList *ret; GtkTreeModel *model; g_return_val_if_fail( SCREEM_IS_FILE_BROWSER( browser ), NULL ); g_return_val_if_fail( iters != NULL, NULL ); ret = NULL; model = GTK_TREE_MODEL( browser->details->model ); while( iters ) { GtkTreeIter *iter; GtkTreeIter it; GtkTreeIter filtered_it; BrowserNode *node; iter = (GtkTreeIter*)iters->data; /* ideally we would check recursion here by seeing if iter has children, however if the file browser is in flat mode then there will not be any children present so we need to check flat mode later on */ if( recurse && gtk_tree_model_iter_has_child( browser->details->sorted, iter ) ) { GtkTreeIter child; if( gtk_tree_model_iter_children( browser->details->sorted, &child, iter ) ) { GList *tmp; tmp = g_list_append( NULL, &child ); ret = g_list_concat( ret, screem_file_browser_get_pathnames( browser, tmp, TRUE ) ); g_list_free( tmp ); } } gtk_tree_model_sort_convert_iter_to_child_iter( GTK_TREE_MODEL_SORT( browser->details->sorted ), &filtered_it, iter ); gtk_tree_model_filter_convert_iter_to_child_iter( GTK_TREE_MODEL_FILTER( browser->details->filtered ), &it, &filtered_it ); gtk_tree_model_get( model, &it, FILE_BROWSER_NODE_COL, &node, -1 ); if( node ) { ret = g_list_append( ret, node->pathname ); } else { gchar *uri; uri = NULL; gtk_tree_model_get( model, &it, FILE_BROWSER_URI_COL, &uri, -1 ); if( uri ) { ret = g_list_append( ret, uri ); } } iters = iters->next; } return ret; } void screem_file_browser_set_sort_func( ScreemFileBrowser *browser, GtkTreeIterCompareFunc func, gpointer data ) { GtkTreeModel *model; model = screem_file_browser_get_model( browser ); gtk_tree_sortable_set_sort_func( GTK_TREE_SORTABLE( model ), 0, func, data, NULL ); gtk_tree_sortable_set_sort_column_id( GTK_TREE_SORTABLE( model ), 0, GTK_SORT_ASCENDING ); } void screem_file_browser_file_mod( ScreemFileBrowser *browser, const gchar *directory, const gchar *file, GnomeVFSMonitorEventType type ) { directory_monitor_cb( NULL, directory, file, type, browser ); } void screem_file_browser_set_show_hidden( ScreemFileBrowser *browser, gboolean show ) { ScreemFileBrowserDetails *details; g_return_if_fail( SCREEM_IS_FILE_BROWSER( browser ) ); details = browser->details; details->show_hidden = show; gtk_tree_model_foreach( GTK_TREE_MODEL( details->model ), (GtkTreeModelForeachFunc)refilter, browser ); } void screem_file_browser_set_show_backups( ScreemFileBrowser *browser, gboolean show ) { ScreemFileBrowserDetails *details; g_return_if_fail( SCREEM_IS_FILE_BROWSER( browser ) ); details = browser->details; details->show_backups = show; gtk_tree_model_foreach( GTK_TREE_MODEL( details->model ), (GtkTreeModelForeachFunc)refilter, browser ); } /* override default backup file suffix of ~ */ void screem_file_browser_set_backup_suffix( ScreemFileBrowser *browser, const gchar *suffix ) { ScreemFileBrowserDetails *details; g_return_if_fail( SCREEM_IS_FILE_BROWSER( browser ) ); details = browser->details; g_free( details->backup_suffix ); if( suffix ) { details->backup_suffix = g_strdup( suffix ); } else { details->backup_suffix = g_strdup( "~" ); } gtk_tree_model_filter_refilter( GTK_TREE_MODEL_FILTER( details->filtered ) ); } void screem_file_browser_cleanup( void ) { if( mime_cache ) { g_string_chunk_free( mime_cache ); } } /* static stuff */ static BrowserNode *browser_node_new( ScreemFileBrowser *browser, gint depth, BrowserNodeType type, const gchar *uri, const gchar *mime_type ) { BrowserNode *node; gchar *temp; node = g_new0( BrowserNode, 1 ); node->type = type; node->browser = browser; g_assert( uri != NULL ); if( mime_type ) { node->mime_type = mime_type; } node->pathname = g_filename_to_utf8( uri, -1, NULL, NULL, NULL ); temp = g_path_get_basename( node->pathname ); if( temp ) { node->basename = gnome_vfs_unescape_string_for_display( temp ); g_free( temp ); } if( ! node->basename || *node->basename == '\0' ) { g_free( node->basename ); node->basename = g_strdup( G_DIR_SEPARATOR_S ); } mime_type = g_string_chunk_insert_const( mime_cache, node->mime_type ); node->mime_type = mime_type; if( type == BROWSER_NODE_DIRECTORY && depth == 0 ) { /* don't monitor /dev, /proc, or /sys */ gchar *monuri; monuri = gnome_vfs_get_local_path_from_uri( uri ); if( ! monuri ) { monuri = g_strdup( uri ); } if( strncmp( "/dev", monuri, strlen( "/dev" ) ) && strncmp( "/sys", monuri, strlen( "/sys" ) ) && strncmp( "/proc", monuri, strlen( "/proc" ) ) ) { gnome_vfs_monitor_add( &node->monitor, uri, GNOME_VFS_MONITOR_DIRECTORY, directory_monitor_cb, browser ); } g_free( monuri ); } return node; } static void browser_node_free( BrowserNode *node ) { g_return_if_fail( node != NULL ); g_free( node->pathname ); g_free( node->basename ); if( node->monitor ) { gnome_vfs_monitor_cancel( node->monitor ); } g_free( node ); } static void scan_directory_add_node( ScreemFileBrowser *browser, GtkTreeIter *parent, const gchar *uri, const gchar *mime_type, guint depth, BrowserNodeType type, GtkTreeIter *it ) { ScreemFileBrowserDetails *details; BrowserNode *node; GdkPixbuf *pixbuf; GtkTreeStore *model; gboolean visible; g_return_if_fail( SCREEM_IS_FILE_BROWSER( browser ) ); g_return_if_fail( parent != NULL || type == BROWSER_NODE_DIRECTORY); g_return_if_fail( it != NULL ); g_return_if_fail( uri != NULL ); g_return_if_fail( mime_type != NULL ); details = browser->details; model = details->model; gtk_tree_store_append( model, it, parent ); pixbuf = screem_icon_cache_get_pixbuf( details->cache, uri, mime_type, SCREEM_ICON_CACHE_DEFAULT_SIZE ); node = browser_node_new( browser, depth, type, uri, mime_type ); gtk_tree_store_set( model, it, FILE_BROWSER_NAME_COL, node->basename, FILE_BROWSER_NODE_COL, node, FILE_BROWSER_ICON_COL, pixbuf, FILE_BROWSER_URI_COL, node->pathname, FILE_BROWSER_MIME_COL, node->mime_type, -1 ); visible = screem_file_browser_filter( GTK_TREE_MODEL( model ), it, browser ); gtk_tree_store_set( model, it, FILE_BROWSER_SHOW_COL, visible, -1 ); if( pixbuf ) { g_object_unref( pixbuf ); } g_signal_emit( G_OBJECT( browser ), screem_file_browser_signals[ ADDED ], 0, node->pathname, node->mime_type, it ); } static void scan_directory_add_file( ScreemFileBrowser *browser, GtkTreeIter *parent, const gchar *uri, const gchar *mime_type, guint depth ) { GtkTreeIter it; scan_directory_add_node( browser, parent, uri, mime_type, depth, BROWSER_NODE_FILE, &it ); } static void scan_directory_add_dir( ScreemFileBrowser *browser, GtkTreeIter *parent, const gchar *uri, const gchar *mime_type, guint depth, GtkTreeIter *it ) { scan_directory_add_node( browser, parent, uri, mime_type, depth, BROWSER_NODE_DIRECTORY, it ); } static void scan_directory( ScreemFileBrowser *browser, GtkTreeIter *parent, const gchar *uri, const gchar *mime_type, guint depth, int mode ) { GnomeVFSResult result; GnomeVFSDirectoryHandle *handle; GnomeVFSFileInfo *info; GtkTreeIter *root; gchar *parent_uri; GtkTreeModel *model; g_return_if_fail( SCREEM_IS_FILE_BROWSER( browser ) ); model = GTK_TREE_MODEL( browser->details->model ); g_return_if_fail( GTK_IS_TREE_MODEL( model ) ); g_return_if_fail( uri != NULL ); root = parent; parent_uri = NULL; if( parent ) { BrowserNode *node; gtk_tree_model_get( model, parent, FILE_BROWSER_NODE_COL, &node, -1 ); if( node ) { parent_uri = g_strdup( node->pathname ); } } result = GNOME_VFS_ERROR_NOT_A_DIRECTORY; handle = NULL; info = NULL; if( mime_type && ! strcmp( "x-directory/normal", mime_type ) ) { result = GNOME_VFS_OK; if( (mode & FILE_BROWSE_RECURSE) || ( depth == 0 ) ) { info = gnome_vfs_file_info_new(); result = gnome_vfs_directory_open( &handle, uri, GNOME_VFS_FILE_INFO_FOLLOW_LINKS | GNOME_VFS_FILE_INFO_GET_MIME_TYPE | GNOME_VFS_FILE_INFO_FORCE_FAST_MIME_TYPE ); } } if( result == GNOME_VFS_ERROR_NOT_A_DIRECTORY ) { /* normal file */ scan_directory_add_file( browser, parent, uri, mime_type, depth ); } else if( result == GNOME_VFS_OK ){ GtkTreeIter it; /* append if parent uri != uri */ if( ! parent_uri || strcmp( uri, parent_uri ) ) { scan_directory_add_dir( browser, parent, uri, mime_type, depth, &it ); } else if( parent_uri ) { it = *root; } if( ! handle ) { /* just add a dummy node */ GtkTreeIter iter; gtk_tree_store_append( GTK_TREE_STORE( model ), &iter, &it ); gtk_tree_store_set( GTK_TREE_STORE( model ), &iter, FILE_BROWSER_SHOW_COL, TRUE, -1 ); } else { const gchar *ccat; guint ndepth; ccat = ""; if( uri[ strlen( uri ) - 1 ] != '/' ) { ccat = "/"; } ndepth = depth + 1; while( ( result = gnome_vfs_directory_read_next( handle, info ) == GNOME_VFS_OK ) ) { if( strcmp( info->name, "." ) && strcmp( info->name, ".." ) ) { gchar *nexturi; nexturi = g_strconcat( uri, ccat, info->name, NULL ); scan_directory( browser, &it, nexturi, info->mime_type, ndepth, mode ); g_free( nexturi ); } } gnome_vfs_directory_close( handle ); } } if( info ) { gnome_vfs_file_info_unref( info ); } g_free( parent_uri ); } static void directory_monitor_cb( GnomeVFSMonitorHandle *handle, const gchar *monitor_uri, const gchar *info_uri, GnomeVFSMonitorEventType type, gpointer data ) { ScreemFileBrowser *browser; GtkTreeIter pit; GtkTreeIter it; GtkTreeIter iter; GtkTreeIter *parent; GtkTreeModel *model; GnomeVFSFileInfo *info; int mode; BrowserNode *node; GnomeVFSResult res; if( ! data ) { return; } browser = SCREEM_FILE_BROWSER( data ); mode = browser->details->mode; switch( type ) { case GNOME_VFS_MONITOR_EVENT_CHANGED: break; case GNOME_VFS_MONITOR_EVENT_DELETED: model = GTK_TREE_MODEL( browser->details->model ); if( find_iter( model, info_uri, NULL, &it ) ) { gtk_tree_model_get( model, &it, FILE_BROWSER_NODE_COL, &node, -1 ); g_signal_emit( G_OBJECT( browser ), screem_file_browser_signals[ REMOVED ], 0, info_uri, &it ); gtk_tree_store_remove( GTK_TREE_STORE( model ), &it ); if( node ) { browser_node_free( node ); } } break; case GNOME_VFS_MONITOR_EVENT_STARTEXECUTING: break; case GNOME_VFS_MONITOR_EVENT_STOPEXECUTING: break; case GNOME_VFS_MONITOR_EVENT_CREATED: model = GTK_TREE_MODEL( browser->details->model ); /* check that we don't already have a node for info_uri */ if( find_iter( model, info_uri, NULL, &iter ) ) { /* we do, don't do anything */ break; } parent = NULL; if( find_iter( model, monitor_uri, NULL, &pit ) ) { /* got the parent */ parent = &pit; } if( ! parent ) { break; } /* is info_uri a directory? */ info = gnome_vfs_file_info_new(); res = gnome_vfs_get_file_info( info_uri, info, GNOME_VFS_FILE_INFO_GET_MIME_TYPE | GNOME_VFS_FILE_INFO_FORCE_FAST_MIME_TYPE ); /* file may have disappeared between notification * signal being sent, and us attempting to get file * info */ if( res != GNOME_VFS_OK ) { gnome_vfs_file_info_unref( info ); break; } if( info->type == GNOME_VFS_FILE_TYPE_DIRECTORY ) { if( ! (mode & FILE_BROWSE_RECURSE) ) { /* dummy node */ gtk_tree_store_append( GTK_TREE_STORE( model ), &iter, parent ); gtk_tree_store_set( GTK_TREE_STORE( model ), &iter, FILE_BROWSER_SHOW_COL, TRUE, -1 ); } else { /* scan at depth 1 to * avoid clearing */ scan_directory( browser, parent, info_uri, "x-directory/normal", 1, mode ); } } else { scan_directory_add_file( browser, parent, info_uri, info->mime_type, 1 ); } gnome_vfs_file_info_unref( info ); break; case GNOME_VFS_MONITOR_EVENT_METADATA_CHANGED: break; default: break; } } static gboolean find_iter( GtkTreeModel *model, const gchar *uri, GtkTreeIter *parent, GtkTreeIter *it ) { gboolean got; gboolean found = FALSE; GnomeVFSURI *a; g_return_val_if_fail( GTK_IS_TREE_MODEL( model ), FALSE ); g_return_val_if_fail( uri != NULL, FALSE ); g_return_val_if_fail( it != NULL, FALSE ); if( ! parent ) { got = gtk_tree_model_get_iter_first( model, it ); } else { got = gtk_tree_model_iter_children( model, it, parent ); } a = gnome_vfs_uri_new( uri ); while( got && ! found ) { BrowserNode *node; GnomeVFSURI *b; gtk_tree_model_get( model, it, FILE_BROWSER_NODE_COL, &node, -1 ); if( node && node->pathname ) { b = gnome_vfs_uri_new( node->pathname ); found = gnome_vfs_uri_equal( a, b ); gnome_vfs_uri_unref( b ); } if( ! found ) { GtkTreeIter cit; if( gtk_tree_model_iter_has_child( model, it ) ) { if( find_iter( model, uri, it, &cit ) ) { /* found it */ *it = cit; found = TRUE; } } } if( ! found ) { got = gtk_tree_model_iter_next( model, it ); } } gnome_vfs_uri_unref( a ); return found; } static void screem_file_browser_set_icons( ScreemFileBrowser *browser, GtkTreeIter *it ) { GtkTreeModel *model; GtkTreeIter rit; g_return_if_fail( SCREEM_IS_FILE_BROWSER( browser ) ); model = GTK_TREE_MODEL( browser->details->model ); if( ! it ) { if( gtk_tree_model_get_iter_first( model, &rit ) ) { it = &rit; } } if( it ) { BrowserNode *node; GtkTreeIter cit; gchar *uri; GdkPixbuf *pixbuf; const gchar *pathname; const gchar *mimetype; gtk_tree_model_get( model, it, FILE_BROWSER_NODE_COL, &node, FILE_BROWSER_URI_COL, &uri, -1 ); pathname = mimetype = NULL; if( node ) { pathname = node->pathname; mimetype = node->mime_type; } if( uri && node ) { pixbuf = screem_icon_cache_get_pixbuf( browser->details->cache, uri, mimetype, SCREEM_ICON_CACHE_DEFAULT_SIZE ); gtk_tree_store_set( GTK_TREE_STORE( model ), it, FILE_BROWSER_MIME_COL, mimetype, FILE_BROWSER_ICON_COL, pixbuf, -1 ); if( pixbuf ) { g_object_unref( pixbuf ); } } g_signal_emit( G_OBJECT( browser ), screem_file_browser_signals[ ICON_CHANGE ], 0, pathname, mimetype, it ); if( gtk_tree_model_iter_has_child( model, it ) ) { gtk_tree_model_iter_children( model, &cit, it); screem_file_browser_set_icons( browser, &cit ); } if( gtk_tree_model_iter_next( model, it ) ) { screem_file_browser_set_icons( browser, it ); } g_free( uri ); } } static void screem_file_browser_theme_change( ScreemIconCache *cache, gpointer data ) { ScreemFileBrowser *browser; browser = SCREEM_FILE_BROWSER( data ); screem_file_browser_set_icons( browser, NULL ); } static gint screem_file_browser_compare_func( GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer data ) { BrowserNode *anode; BrowserNode *bnode; int ret = 0; gtk_tree_model_get( model, a, FILE_BROWSER_NODE_COL, &anode, -1 ); gtk_tree_model_get( model, b, FILE_BROWSER_NODE_COL, &bnode, -1 ); if( anode == bnode ) { return 0; } else if( anode && ! bnode ) { ret = 1; } else if( bnode && ! anode ) { ret = -1; } else if( ! anode && ! bnode ) { ret = 0; } else if( anode->type == BROWSER_NODE_DIRECTORY && bnode->type != BROWSER_NODE_DIRECTORY ) { ret = -1; } else if( bnode->type == BROWSER_NODE_DIRECTORY && anode->type != BROWSER_NODE_DIRECTORY ) { ret = 1; } else { ret = strcmp( anode->pathname, bnode->pathname ); } return ret; } static GList *screem_file_browser_recurse( const gchar *pathname ) { GList *ret; GnomeVFSDirectoryHandle *handle; GnomeVFSResult result; ret = NULL; result = gnome_vfs_directory_open( &handle, pathname, GNOME_VFS_FILE_INFO_FOLLOW_LINKS ); if( result == GNOME_VFS_OK ) { GnomeVFSFileInfo *info; info = gnome_vfs_file_info_new(); do { result = gnome_vfs_directory_read_next( handle, info ); if( result == GNOME_VFS_OK && *info->name != '.' ) { gchar *temp; temp = g_strconcat( pathname, "/", info->name, NULL ); ret = g_list_append( ret, temp ); ret = g_list_concat( ret, screem_file_browser_recurse( temp ) ); } } while( result == GNOME_VFS_OK ); gnome_vfs_file_info_unref( info ); gnome_vfs_directory_close( handle ); } return ret; } static gboolean screem_file_browser_clear( GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *it, gpointer data ) { ScreemFileBrowser *browser; ScreemFileBrowserDetails *details; BrowserNode *node; gtk_tree_model_get( model, it, FILE_BROWSER_NODE_COL, &node, -1 ); if( node ) { browser_node_free( node ); } browser = SCREEM_FILE_BROWSER( data ); details = browser->details; if( details->filtered ) { gtk_tree_store_set( GTK_TREE_STORE( model ), it, FILE_BROWSER_NODE_COL, NULL, -1 ); } return FALSE; } static gboolean screem_file_browser_filter( GtkTreeModel *model, GtkTreeIter *it, ScreemFileBrowser *browser ) { ScreemFileBrowserDetails *details; BrowserNode *node; const gchar *base; gboolean visible; const gchar *suffix; g_return_val_if_fail( SCREEM_IS_FILE_BROWSER( browser ), FALSE ); g_return_val_if_fail( GTK_IS_TREE_MODEL( model ), FALSE ); g_return_val_if_fail( it != NULL, FALSE ); details = browser->details; if( ! details->filtered ) { return FALSE; } gtk_tree_model_get( model, it, FILE_BROWSER_NODE_COL, &node, -1 ); visible = TRUE; if( node && ( base = node->basename ) ) { if( ! details->show_hidden ) { visible &= *base != '.'; } if( ! details->show_backups ) { suffix = strstr( base, details->backup_suffix ); if( suffix ) { visible &= strcmp( suffix, details->backup_suffix ); } } } return visible; } static gboolean refilter( GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, ScreemFileBrowser *browser ) { gboolean cur; gboolean visible; gtk_tree_model_get( model, iter, FILE_BROWSER_SHOW_COL, &cur, -1 ); visible = screem_file_browser_filter( model, iter, browser ); if( visible != cur ) { gtk_tree_store_set( GTK_TREE_STORE( model ), iter, FILE_BROWSER_SHOW_COL, visible, -1 ); } return FALSE; } /* G Object stuff */ G_DEFINE_TYPE( ScreemFileBrowser, screem_file_browser, G_TYPE_OBJECT ) static void screem_file_browser_finalize( GObject *object ); static void screem_file_browser_set_prop( GObject *object, guint prop_id, const GValue *value, GParamSpec *spec ); static void screem_file_browser_get_prop( GObject *object, guint prop_id, GValue *value, GParamSpec *spec ); static void screem_file_browser_class_init( ScreemFileBrowserClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS( klass ); object_class->finalize = screem_file_browser_finalize; object_class->get_property = screem_file_browser_get_prop; object_class->set_property = screem_file_browser_set_prop; screem_file_browser_signals[ REMOVED ] = g_signal_new( "removed", G_OBJECT_CLASS_TYPE( object_class ), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET( ScreemFileBrowserClass, removed ), NULL, NULL, screem_marshal_VOID__STRING_POINTER, G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_POINTER ); screem_file_browser_signals[ ADDED ] = g_signal_new( "added", G_OBJECT_CLASS_TYPE( object_class ), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET( ScreemFileBrowserClass, added ), NULL, NULL, screem_marshal_VOID__STRING_STRING_POINTER, G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER ); screem_file_browser_signals[ ICON_CHANGE ] = g_signal_new( "icon_change", G_OBJECT_CLASS_TYPE( object_class ), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET( ScreemFileBrowserClass, icon_change ), NULL, NULL, screem_marshal_VOID__STRING_STRING_POINTER, G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER ); } static void screem_file_browser_init( ScreemFileBrowser *browser ) { ScreemFileBrowserDetails *details; if( ! mime_cache ) { mime_cache = g_string_chunk_new( 128 ); } details = browser->details = g_new0( ScreemFileBrowserDetails, 1 ); details->show_hidden = FALSE; details->show_backups = FALSE; details->backup_suffix = g_strdup( "~" ); details->model = gtk_tree_store_new( FILE_BROWSER_MAX_COL, G_TYPE_STRING, GDK_TYPE_PIXBUF, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_BOOLEAN ); details->filtered = gtk_tree_model_filter_new( GTK_TREE_MODEL( details->model ), NULL ); #if 0 gtk_tree_model_filter_set_visible_func( GTK_TREE_MODEL_FILTER( details->filtered ), (GtkTreeModelFilterVisibleFunc)screem_file_browser_filter, browser, NULL ); #else gtk_tree_model_filter_set_visible_column( GTK_TREE_MODEL_FILTER( details->filtered ), FILE_BROWSER_SHOW_COL ); #endif details->sorted = GTK_TREE_MODEL( gtk_tree_model_sort_new_with_model( GTK_TREE_MODEL( details->filtered ) ) ); screem_file_browser_set_sort_func( browser, screem_file_browser_compare_func, browser ); /* quick hack so we can get at the filtered / sorted models * easily from the base model */ g_object_set_data( G_OBJECT( details->model ), "filtered", details->filtered ); g_object_set_data( G_OBJECT( details->model ), "sorted", details->sorted ); } static void screem_file_browser_set_prop( GObject *object, guint prop_id, const GValue *value, GParamSpec *spec ) { ScreemFileBrowser *browser; browser = SCREEM_FILE_BROWSER( object ); switch( prop_id ) { default: break; } } static void screem_file_browser_get_prop( GObject *object, guint prop_id, GValue *value, GParamSpec *spec ) { ScreemFileBrowser *browser; browser = SCREEM_FILE_BROWSER( object ); switch( prop_id ) { default: break; } } static void screem_file_browser_finalize( GObject *object ) { ScreemFileBrowser *browser; ScreemFileBrowserDetails *details; ScreemIconCache *cache; browser = SCREEM_FILE_BROWSER( object ); details = browser->details; g_object_unref( details->sorted ); g_object_unref( details->filtered ); details->filtered = NULL; gtk_tree_model_foreach( GTK_TREE_MODEL( details->model ), screem_file_browser_clear, browser ); gtk_tree_store_clear( GTK_TREE_STORE( details->model ) ); g_object_unref( details->model ); cache = details->cache; if( cache ) { g_signal_handlers_disconnect_matched( G_OBJECT( cache ), G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, browser ); g_object_unref( G_OBJECT( details->cache ) ); } if( details->backup_suffix ) { g_free( details->backup_suffix ); } g_free( details ); G_OBJECT_CLASS( screem_file_browser_parent_class )->finalize( object ); }