diff options
Diffstat (limited to 'src/file-manager')
-rw-r--r-- | src/file-manager/Makefile.am | 4 | ||||
-rw-r--r-- | src/file-manager/fm-directory-view.c | 762 | ||||
-rw-r--r-- | src/file-manager/fm-directory-view.h | 5 | ||||
-rw-r--r-- | src/file-manager/fm-error-reporting.c | 13 | ||||
-rw-r--r-- | src/file-manager/fm-icon-container.c | 33 | ||||
-rw-r--r-- | src/file-manager/fm-icon-view.c | 43 | ||||
-rw-r--r-- | src/file-manager/fm-list-model.c | 51 | ||||
-rw-r--r-- | src/file-manager/fm-list-model.h | 1 | ||||
-rw-r--r-- | src/file-manager/fm-list-view.c | 154 | ||||
-rw-r--r-- | src/file-manager/fm-properties-window.c | 107 | ||||
-rw-r--r-- | src/file-manager/fm-properties-window.h | 5 | ||||
-rw-r--r-- | src/file-manager/fm-search-list-view.c | 99 | ||||
-rw-r--r-- | src/file-manager/fm-tree-model.c | 1797 | ||||
-rw-r--r-- | src/file-manager/fm-tree-model.h | 88 | ||||
-rw-r--r-- | src/file-manager/fm-tree-view.c | 1305 | ||||
-rw-r--r-- | src/file-manager/fm-tree-view.h | 53 | ||||
-rw-r--r-- | src/file-manager/nautilus-directory-view-ui.xml | 24 | ||||
-rw-r--r-- | src/file-manager/nautilus-indexing-info.c | 30 |
18 files changed, 4323 insertions, 251 deletions
diff --git a/src/file-manager/Makefile.am b/src/file-manager/Makefile.am index f96ea8ea1..03640c88a 100644 --- a/src/file-manager/Makefile.am +++ b/src/file-manager/Makefile.am @@ -27,6 +27,8 @@ libnautilus_file_manager_la_SOURCES= \ fm-search-list-view.c \ nautilus-indexing-info.c \ fm-bonobo-provider.h \ + fm-tree-model.c \ + fm-tree-view.c \ fm-desktop-icon-view.h \ fm-directory-view.h \ fm-error-reporting.h \ @@ -38,6 +40,8 @@ libnautilus_file_manager_la_SOURCES= \ fm-properties-window.h \ fm-search-list-view.h \ nautilus-indexing-info.h \ + fm-tree-model.h \ + fm-tree-view.h \ $(NULL) uidir = $(datadir)/gnome-2.0/ui diff --git a/src/file-manager/fm-directory-view.c b/src/file-manager/fm-directory-view.c index b757e08a6..336a8fad7 100644 --- a/src/file-manager/fm-directory-view.c +++ b/src/file-manager/fm-directory-view.c @@ -141,6 +141,8 @@ #define FM_DIRECTORY_VIEW_COMMAND_MEDIA_PROPERTIES_VOLUME_CONDITIONAL "/commands/Media Properties Conditional" #define FM_DIRECTORY_VIEW_MENU_PATH_OPEN_WITH "/menu/File/Open Placeholder/Open With" +#define FM_DIRECTORY_VIEW_MENU_PATH_NEW_DOCUMENTS "/menu/File/New Items Placeholder/New Documents" +#define FM_DIRECTORY_VIEW_MENU_PATH_NEW_DOCUMENTS_NO_TEMPLATES "/menu/File/New Items Placeholder/New Documents/No Templates" #define FM_DIRECTORY_VIEW_MENU_PATH_SCRIPTS "/menu/File/Open Placeholder/Scripts" #define FM_DIRECTORY_VIEW_MENU_PATH_TRASH "/menu/Edit/Dangerous File Items Placeholder/Trash" #define FM_DIRECTORY_VIEW_MENU_PATH_DELETE "/menu/Edit/Dangerous File Items Placeholder/Delete" @@ -153,6 +155,8 @@ #define FM_DIRECTORY_VIEW_MENU_PATH_OTHER_VIEWER "/menu/File/Open Placeholder/Open With/OtherViewer" #define FM_DIRECTORY_VIEW_MENU_PATH_SCRIPTS_PLACEHOLDER "/menu/File/Open Placeholder/Scripts/Scripts Placeholder" #define FM_DIRECTORY_VIEW_MENU_PATH_SCRIPTS_SEPARATOR "/menu/File/Open Placeholder/Scripts/After Scripts" +#define FM_DIRECTORY_VIEW_MENU_PATH_NEW_DOCUMENTS_PLACEHOLDER "/menu/File/New Items Placeholder/New Documents/New Documents Placeholder" +#define FM_DIRECTORY_VIEW_MENU_PATH_NEW_DOCUMENTS_SEPARATOR "/menu/File/New Items Placeholder/New Documents/After New Documents" #define FM_DIRECTORY_VIEW_MENU_PATH_CUT_FILES "/menu/Edit/Cut" #define FM_DIRECTORY_VIEW_MENU_PATH_COPY_FILES "/menu/Edit/Copy" #define FM_DIRECTORY_VIEW_MENU_PATH_PASTE_FILES "/menu/Edit/Paste" @@ -164,6 +168,10 @@ #define FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_SCRIPTS "/popups/background/Before Zoom Items/Scripts" #define FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_SCRIPTS_PLACEHOLDER "/popups/background/Before Zoom Items/Scripts/Scripts Placeholder" #define FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_SCRIPTS_SEPARATOR "/popups/background/Before Zoom Items/Scripts/After Scripts" +#define FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_NEW_DOCUMENTS "/popups/background/Before Zoom Items/New Documents" +#define FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_NEW_DOCUMENTS_NO_TEMPLATES "/popups/background/Before Zoom Items/New Documents/No Templates" +#define FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_NEW_DOCUMENTS_PLACEHOLDER "/popups/background/Before Zoom Items/New Documents/New Documents Placeholder" +#define FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_NEW_DOCUMENTS_SEPARATOR "/popups/background/Before Zoom Items/New Documents/After New Documents" #define FM_DIRECTORY_VIEW_POPUP_PATH_APPLICATIONS_PLACEHOLDER "/popups/selection/Open Placeholder/Open With/Applications Placeholder" #define FM_DIRECTORY_VIEW_POPUP_PATH_BEFORE_VIEWERS_SEPARATOR "/popups/selection/Open Placeholder/Open With/Before Viewers" @@ -201,6 +209,9 @@ static gboolean confirm_trash_auto_value; static char *scripts_directory_uri; static int scripts_directory_uri_length; +static char *templates_directory_uri; +static int templates_directory_uri_length; + struct FMDirectoryViewDetails { NautilusView *nautilus_view; @@ -211,9 +222,11 @@ struct FMDirectoryViewDetails BonoboUIComponent *ui; GList *scripts_directory_list; + GList *templates_directory_list; guint display_selection_idle_id; guint update_menus_timeout_id; + guint update_status_idle_id; guint display_pending_idle_id; @@ -237,6 +250,7 @@ struct FMDirectoryViewDetails gboolean menus_merged; gboolean menu_states_untrustworthy; gboolean scripts_invalid; + gboolean templates_invalid; gboolean reported_load_error; gboolean sort_directories_first; @@ -331,6 +345,8 @@ static void zoomable_zoom_to_fit_callback (BonoboZoomable static void schedule_update_menus (FMDirectoryView *view); static void schedule_update_menus_callback (gpointer callback_data); static void remove_update_menus_timeout_callback (FMDirectoryView *view); +static void schedule_update_status (FMDirectoryView *view); +static void remove_update_status_idle_callback (FMDirectoryView *view); static void schedule_idle_display_of_pending_files (FMDirectoryView *view); static void unschedule_idle_display_of_pending_files (FMDirectoryView *view); static void unschedule_display_of_pending_files (FMDirectoryView *view); @@ -362,6 +378,7 @@ EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, clear) EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, file_changed) EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, get_background_widget) EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, get_selection) +EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, get_item_count) EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, is_empty) EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, reset_to_defaults) EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, restore_default_zoom_level) @@ -386,6 +403,12 @@ typedef struct { FMDirectoryView *directory_view; } ScriptLaunchParameters; +typedef struct { + NautilusFile *file; + FMDirectoryView *directory_view; +} CreateTemplateParameters; + + static ApplicationLaunchParameters * application_launch_parameters_new (GnomeVFSMimeApplication *application, NautilusFile *file, @@ -460,6 +483,30 @@ script_launch_parameters_free (ScriptLaunchParameters *parameters) g_free (parameters); } +static CreateTemplateParameters * +create_template_parameters_new (NautilusFile *file, + FMDirectoryView *directory_view) +{ + CreateTemplateParameters *result; + + result = g_new0 (CreateTemplateParameters, 1); + g_object_ref (directory_view); + result->directory_view = directory_view; + nautilus_file_ref (file); + result->file = file; + + return result; +} + +static void +create_templates_parameters_free (CreateTemplateParameters *parameters) +{ + g_object_unref (parameters->directory_view); + nautilus_file_unref (parameters->file); + g_free (parameters); +} + + /* Returns the GtkWindow that this directory view occupies, or NULL * if at the moment this directory view is not in a GtkWindow or the * GtkWindow cannot be determined. Primarily used for parenting dialogs. @@ -485,19 +532,20 @@ fm_directory_view_confirm_multiple_windows (FMDirectoryView *view, int count) GtkDialog *dialog; char *prompt; char *title; + char *detail; int response; if (count <= SILENT_WINDOW_OPEN_LIMIT) { return TRUE; } - prompt = g_strdup_printf (_("This will open %d separate windows. " - "Are you sure you want to do this?"), count); title = g_strdup_printf (_("Open %d Windows?"), count); - dialog = eel_show_yes_no_dialog (prompt, title, + prompt = _("Are you sure you want to open all files?"); + detail = g_strdup_printf (_("This will open %d separate windows."), count); + dialog = eel_show_yes_no_dialog (prompt, detail, title, GTK_STOCK_OK, GTK_STOCK_CANCEL, fm_directory_view_get_containing_window (view)); - g_free (prompt); + g_free (detail); g_free (title); response = gtk_dialog_run (dialog); @@ -552,10 +600,28 @@ open_callback (BonoboUIComponent *component, gpointer callback_data, const char view = FM_DIRECTORY_VIEW (callback_data); selection = fm_directory_view_get_selection (view); - fm_directory_view_activate_files (view, selection, Nautilus_ViewFrame_OPEN_ACCORDING_TO_MODE, 0); + fm_directory_view_activate_files (view, selection, + Nautilus_ViewFrame_OPEN_ACCORDING_TO_MODE, + 0); + nautilus_file_list_free (selection); +} + +static void +open_close_parent_callback (BonoboUIComponent *component, gpointer callback_data, const char *verb) +{ + GList *selection; + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + + selection = fm_directory_view_get_selection (view); + fm_directory_view_activate_files (view, selection, + Nautilus_ViewFrame_OPEN_ACCORDING_TO_MODE, + Nautilus_ViewFrame_OPEN_FLAG_CLOSE_BEHIND); nautilus_file_list_free (selection); } + static void open_alternate_callback (BonoboUIComponent *component, gpointer callback_data, const char *verb) { @@ -840,6 +906,7 @@ confirm_delete_directly (FMDirectoryView *view, dialog = eel_show_yes_no_dialog (prompt, + _("If you delete an item, it is permanently lost."), _("Delete?"), GTK_STOCK_DELETE, GTK_STOCK_CANCEL, fm_directory_view_get_containing_window (view)); @@ -1049,6 +1116,14 @@ new_folder_callback (BonoboUIComponent *component, gpointer callback_data, const } static void +new_empty_file_callback (BonoboUIComponent *component, gpointer callback_data, const char *verb) +{ + g_assert (FM_IS_DIRECTORY_VIEW (callback_data)); + + fm_directory_view_new_file (FM_DIRECTORY_VIEW (callback_data), NULL); +} + +static void new_launcher_callback (BonoboUIComponent *component, gpointer callback_data, const char *verb) { char *parent_uri; @@ -1080,7 +1155,7 @@ open_properties_window_callback (BonoboUIComponent *component, gpointer callback view = FM_DIRECTORY_VIEW (callback_data); selection = fm_directory_view_get_selection (view); - fm_properties_window_present (selection, view); + fm_properties_window_present (selection, GTK_WIDGET (view)); nautilus_file_list_free (selection); } @@ -1262,6 +1337,17 @@ set_up_scripts_directory_global (void) } static void +set_up_templates_directory_global (void) +{ + if (templates_directory_uri != NULL) { + return; + } + + templates_directory_uri = nautilus_get_templates_directory_uri (); + templates_directory_uri_length = strlen (templates_directory_uri); +} + +static void create_scripts_directory (void) { char *gnome1_path, *gnome1_uri_str; @@ -1307,6 +1393,19 @@ scripts_added_or_changed_callback (NautilusDirectory *directory, } static void +templates_added_or_changed_callback (NautilusDirectory *directory, + GList *files, + gpointer callback_data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (callback_data); + + view->details->templates_invalid = TRUE; + schedule_update_menus (view); +} + +static void icons_changed_callback (gpointer callback_data) { FMDirectoryView *view; @@ -1314,57 +1413,100 @@ icons_changed_callback (gpointer callback_data) view = FM_DIRECTORY_VIEW (callback_data); view->details->scripts_invalid = TRUE; + view->details->templates_invalid = TRUE; schedule_update_menus (view); } static void -add_directory_to_scripts_directory_list (FMDirectoryView *view, - NautilusDirectory *directory) +add_directory_to_directory_list (FMDirectoryView *view, + NautilusDirectory *directory, + GList **directory_list, + GCallback changed_callback) { NautilusFileAttributes attributes; - if (g_list_find (view->details->scripts_directory_list, directory) == NULL) { + if (g_list_find (*directory_list, directory) == NULL) { nautilus_directory_ref (directory); attributes = nautilus_icon_factory_get_required_file_attributes (); attributes |= NAUTILUS_FILE_ATTRIBUTE_CAPABILITIES | NAUTILUS_FILE_ATTRIBUTE_DIRECTORY_ITEM_COUNT; - nautilus_directory_file_monitor_add (directory, &view->details->scripts_directory_list, + nautilus_directory_file_monitor_add (directory, directory_list, FALSE, FALSE, attributes, - scripts_added_or_changed_callback, view); + (NautilusDirectoryCallback)changed_callback, view); g_signal_connect_object (directory, "files_added", - G_CALLBACK (scripts_added_or_changed_callback), view, 0); + G_CALLBACK (changed_callback), view, 0); g_signal_connect_object (directory, "files_changed", - G_CALLBACK (scripts_added_or_changed_callback), view, 0); + G_CALLBACK (changed_callback), view, 0); - view->details->scripts_directory_list = g_list_append - (view->details->scripts_directory_list, directory); + *directory_list = g_list_append (*directory_list, directory); } } static void -remove_directory_from_scripts_directory_list (FMDirectoryView *view, - NautilusDirectory *directory) +remove_directory_from_directory_list (FMDirectoryView *view, + NautilusDirectory *directory, + GList **directory_list, + GCallback changed_callback) { - view->details->scripts_directory_list = g_list_remove - (view->details->scripts_directory_list, directory); + *directory_list = g_list_remove (*directory_list, directory); g_signal_handlers_disconnect_by_func (directory, - G_CALLBACK (scripts_added_or_changed_callback), + G_CALLBACK (changed_callback), view); - nautilus_directory_file_monitor_remove (directory, &view->details->scripts_directory_list); + nautilus_directory_file_monitor_remove (directory, directory_list); nautilus_directory_unref (directory); } + +static void +add_directory_to_scripts_directory_list (FMDirectoryView *view, + NautilusDirectory *directory) +{ + add_directory_to_directory_list (view, directory, + &view->details->scripts_directory_list, + G_CALLBACK (scripts_added_or_changed_callback)); +} + +static void +remove_directory_from_scripts_directory_list (FMDirectoryView *view, + NautilusDirectory *directory) +{ + remove_directory_from_directory_list (view, directory, + &view->details->scripts_directory_list, + G_CALLBACK (scripts_added_or_changed_callback)); +} + +static void +add_directory_to_templates_directory_list (FMDirectoryView *view, + NautilusDirectory *directory) +{ + add_directory_to_directory_list (view, directory, + &view->details->templates_directory_list, + G_CALLBACK (templates_added_or_changed_callback)); +} + +static void +remove_directory_from_templates_directory_list (FMDirectoryView *view, + NautilusDirectory *directory) +{ + remove_directory_from_directory_list (view, directory, + &view->details->templates_directory_list, + G_CALLBACK (templates_added_or_changed_callback)); +} + + + static void fm_directory_view_init (FMDirectoryView *view) { static gboolean setup_autos = FALSE; NautilusDirectory *scripts_directory; + NautilusDirectory *templates_directory; if (!setup_autos) { setup_autos = TRUE; @@ -1387,11 +1529,15 @@ fm_directory_view_init (FMDirectoryView *view) view->details->nautilus_view = nautilus_view_new (GTK_WIDGET (view)); set_up_scripts_directory_global (); - scripts_directory = nautilus_directory_get (scripts_directory_uri); add_directory_to_scripts_directory_list (view, scripts_directory); nautilus_directory_unref (scripts_directory); + set_up_templates_directory_global (); + templates_directory = nautilus_directory_get (templates_directory_uri); + add_directory_to_templates_directory_list (view, templates_directory); + nautilus_directory_unref (templates_directory); + view->details->zoomable = bonobo_zoomable_new (); bonobo_zoomable_set_parameters_full (view->details->zoomable, 0.0, .25, 4.0, TRUE, TRUE, FALSE, @@ -1489,6 +1635,12 @@ fm_directory_view_finalize (GObject *object) remove_directory_from_scripts_directory_list (view, node->data); } + for (node = view->details->templates_directory_list; node != NULL; node = next) { + next = node->next; + remove_directory_from_templates_directory_list (view, node->data); + } + + nautilus_directory_unref (view->details->model); view->details->model = NULL; nautilus_file_unref (view->details->directory_as_file); @@ -1498,6 +1650,7 @@ fm_directory_view_finalize (GObject *object) } remove_update_menus_timeout_callback (view); + remove_update_status_idle_callback (view); fm_directory_view_ignore_hidden_file_preferences (view); @@ -1541,7 +1694,7 @@ fm_directory_view_display_selection_info (FMDirectoryView *view) GnomeVFSFileSize non_folder_size; guint non_folder_count, folder_count, folder_item_count; gboolean folder_item_count_known; - guint item_count; + guint file_item_count; GList *p; char *first_item_name; char *non_folder_str; @@ -1568,8 +1721,8 @@ fm_directory_view_display_selection_info (FMDirectoryView *view) file = p->data; if (nautilus_file_is_directory (file)) { folder_count++; - if (nautilus_file_get_directory_item_count (file, &item_count, NULL)) { - folder_item_count += item_count; + if (nautilus_file_get_directory_item_count (file, &file_item_count, NULL)) { + folder_item_count += file_item_count; } else { folder_item_count_known = FALSE; } @@ -1656,7 +1809,23 @@ fm_directory_view_display_selection_info (FMDirectoryView *view) } if (folder_count == 0 && non_folder_count == 0) { - status_string = g_strdup (""); + char *free_space_str; + char *item_count_str; + guint item_count; + + item_count = fm_directory_view_get_item_count (view); + + item_count_str = g_strdup_printf (ngettext ("%u item", "%u items", item_count), item_count); + + free_space_str = nautilus_file_get_volume_free_space (view->details->directory_as_file); + if (free_space_str != NULL) { + status_string = g_strdup_printf (_("%s, Free space: %s"), item_count_str, free_space_str); + g_free (free_space_str); + g_free (item_count_str); + } else { + status_string = item_count_str; + } + } else if (folder_count == 0) { status_string = g_strdup (non_folder_str); } else if (non_folder_count == 0) { @@ -1801,14 +1970,14 @@ real_file_limit_reached (FMDirectoryView *view) * no more than the constant limit are displayed. */ message = g_strdup_printf (_("The folder \"%s\" contains more files than " - "Nautilus can handle. Some files will not be " - "displayed."), + "Nautilus can handle."), directory_name); g_free (directory_name); dialog = eel_show_warning_dialog (message, - _("Too Many Files"), - fm_directory_view_get_containing_window (view)); + _("Some files will not be displayed."), + _("Too Many Files"), + fm_directory_view_get_containing_window (view)); g_free (message); } @@ -1836,6 +2005,7 @@ done_loading (FMDirectoryView *view) if (view->details->nautilus_view != NULL) { nautilus_view_report_load_complete (view->details->nautilus_view); schedule_update_menus (view); + schedule_update_status (view); check_for_directory_hard_limit (view); uris_selected = view->details->pending_uris_selected; @@ -2455,6 +2625,9 @@ files_added_callback (NautilusDirectory *directory, view = FM_DIRECTORY_VIEW (callback_data); queue_pending_files (view, files, &view->details->new_added_files); + + /* The number of items could have changed */ + schedule_update_status (view); } static void @@ -2467,6 +2640,9 @@ files_changed_callback (NautilusDirectory *directory, view = FM_DIRECTORY_VIEW (callback_data); queue_pending_files (view, files, &view->details->new_changed_files); + /* The free space or the number of items could have changed */ + schedule_update_status (view); + /* A change in MIME type could affect the Open with menu, for * one thing, so we need to update menus when files change. */ @@ -2777,6 +2953,16 @@ fm_directory_view_get_selection (FMDirectoryView *view) get_selection, (view)); } +guint +fm_directory_view_get_item_count (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_DIRECTORY_VIEW (view), 0); + + return EEL_CALL_METHOD_WITH_RETURN_VALUE + (FM_DIRECTORY_VIEW_CLASS, view, + get_item_count, (view)); +} + /** * fm_directory_view_get_bonobo_ui_container: * @@ -3040,7 +3226,9 @@ fm_directory_view_confirm_deletion (FMDirectoryView *view, GList *uris, gboolean { GtkDialog *dialog; char *prompt; + char *detail; int uri_count; + char *uri; char *file_name; int response; @@ -3050,29 +3238,31 @@ fm_directory_view_confirm_deletion (FMDirectoryView *view, GList *uris, gboolean g_assert (uri_count > 0); if (uri_count == 1) { - file_name = file_name_from_uri ((char *) uris->data); - - prompt = g_strdup_printf (_("\"%s\" cannot be moved to the Trash. Do " - "you want to delete it immediately?"), file_name); + uri = (char *) uris->data; + if (eel_uri_is_desktop (uri)) { + /* Don't ask for desktop icons */ + return TRUE; + } + file_name = file_name_from_uri (uri); + prompt = _("Cannot move file to trash, do you want to delete immediately?"); + detail = g_strdup_printf (_("The file \"%s\" cannot be moved to the trash."), file_name); g_free (file_name); } else { if (all) { - prompt = g_strdup_printf (_("The %d selected items cannot be moved " - "to the Trash. Do you want to delete them " - "immediately?"), uri_count); + prompt = _("Cannot move items to trash, do you want to delete them immediately?"); + detail = g_strdup_printf ("None of the %d selected items can be moved to the Trash", uri_count); } else { - prompt = g_strdup_printf (_("%d of the selected items cannot be moved " - "to the Trash. Do you want to delete those " - "%d items immediately?"), uri_count, uri_count); + prompt = _("Cannot move some items to trash, do you want to delete these immediately?"); + detail = g_strdup_printf ("%d of the selected items cannot be moved to the Trash", uri_count); } } dialog = eel_show_yes_no_dialog (prompt, - _("Delete Immediately?"), GTK_STOCK_DELETE, GTK_STOCK_CANCEL, + detail, _("Delete Immediately?"), GTK_STOCK_DELETE, GTK_STOCK_CANCEL, fm_directory_view_get_containing_window (view)); - g_free (prompt); + g_free (detail); response = gtk_dialog_run (dialog); gtk_object_destroy (GTK_OBJECT (dialog)); @@ -3102,15 +3292,15 @@ confirm_delete_from_trash (FMDirectoryView *view, GList *uris) if (uri_count == 1) { file_name = file_name_from_uri ((char *) uris->data); prompt = g_strdup_printf (_("Are you sure you want to permanently delete \"%s\" " - "from the Trash?"), file_name); + "from the trash?"), file_name); g_free (file_name); } else { prompt = g_strdup_printf (_("Are you sure you want to permanently delete " - "the %d selected items from the Trash?"), uri_count); + "the %d selected items from the trash?"), uri_count); } dialog = eel_show_yes_no_dialog ( - prompt, + prompt, _("If you delete an item, it will be permanently lost."), _("Delete From Trash?"), GTK_STOCK_DELETE, GTK_STOCK_CANCEL, fm_directory_view_get_containing_window (view)); @@ -3219,29 +3409,58 @@ start_renaming_file (FMDirectoryView *view, NautilusFile *file) } static void +rename_file (FMDirectoryView *view, NautilusFile *new_file) +{ + /* no need to select because start_renaming_file selects + * fm_directory_view_select_file (view, new_file); + */ + EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, start_renaming_file, (view, new_file)); + fm_directory_view_reveal_selection (view); +} + +static void reveal_newly_added_folder (FMDirectoryView *view, NautilusFile *new_file, const char *target_uri) { if (nautilus_file_matches_uri (new_file, target_uri)) { g_signal_handlers_disconnect_by_func (view, G_CALLBACK (reveal_newly_added_folder), (void *) target_uri); - /* no need to select because start_renaming_file selects - * fm_directory_view_select_file (view, new_file); - */ - EEL_CALL_METHOD (FM_DIRECTORY_VIEW_CLASS, view, start_renaming_file, (view, new_file)); - fm_directory_view_reveal_selection (view); + rename_file (view, new_file); } } +typedef struct { + FMDirectoryView *directory_view; + GHashTable *added_uris; +} NewFolderData; + + +static void +track_newly_added_uris (FMDirectoryView *view, NautilusFile *new_file, gpointer user_data) +{ + NewFolderData *data; + + data = user_data; + + g_hash_table_insert (data->added_uris, nautilus_file_get_uri (new_file), NULL); +} + static void -new_folder_done (const char *new_folder_uri, gpointer data) +new_folder_done (const char *new_folder_uri, gpointer user_data) { FMDirectoryView *directory_view; NautilusFile *file; char *screen_string; GdkScreen *screen; + NewFolderData *data; - directory_view = (FMDirectoryView *) data; + data = (NewFolderData *)user_data; + + if (new_folder_uri == NULL) { + goto fail; + } + + directory_view = data->directory_view; g_assert (FM_IS_DIRECTORY_VIEW (directory_view)); screen = gtk_widget_get_screen (GTK_WIDGET (directory_view)); @@ -3254,31 +3473,92 @@ new_folder_done (const char *new_folder_uri, gpointer data) screen_string); g_free (screen_string); - /* We need to run after the default handler adds the folder we want to - * operate on. The ADD_FILE signal is registered as G_SIGNAL_RUN_LAST, so we - * must use connect_after. - */ - g_signal_connect_data (directory_view, - "add_file", - G_CALLBACK (reveal_newly_added_folder), - g_strdup (new_folder_uri), - (GClosureNotify)g_free, - G_CONNECT_AFTER); + g_signal_handlers_disconnect_by_func (directory_view, + G_CALLBACK (track_newly_added_uris), + (void *) data); + + if (g_hash_table_lookup_extended (data->added_uris, new_folder_uri, NULL, NULL)) { + /* The file was already added */ + rename_file (directory_view, file); + } else { + /* We need to run after the default handler adds the folder we want to + * operate on. The ADD_FILE signal is registered as G_SIGNAL_RUN_LAST, so we + * must use connect_after. + */ + g_signal_connect_data (directory_view, + "add_file", + G_CALLBACK (reveal_newly_added_folder), + g_strdup (new_folder_uri), + (GClosureNotify)g_free, + G_CONNECT_AFTER); + } + + fail: + g_hash_table_destroy (data->added_uris); + g_free (data); } void fm_directory_view_new_folder (FMDirectoryView *directory_view) { char *parent_uri; + NewFolderData *data; + + data = g_new (NewFolderData, 1); + data->directory_view = directory_view; + data->added_uris = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + + g_signal_connect_data (directory_view, + "add_file", + G_CALLBACK (track_newly_added_uris), + data, + (GClosureNotify)NULL, + G_CONNECT_AFTER); parent_uri = fm_directory_view_get_backing_uri (directory_view); nautilus_file_operations_new_folder (GTK_WIDGET (directory_view), parent_uri, - new_folder_done, directory_view); + new_folder_done, data); g_free (parent_uri); } +void +fm_directory_view_new_file (FMDirectoryView *directory_view, + NautilusFile *source) +{ + char *parent_uri; + char *source_uri; + NewFolderData *data; + + data = g_new (NewFolderData, 1); + data->directory_view = directory_view; + data->added_uris = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + + g_signal_connect_data (directory_view, + "add_file", + G_CALLBACK (track_newly_added_uris), + data, + (GClosureNotify)NULL, + G_CONNECT_AFTER); + + + source_uri = NULL; + if (source != NULL) { + source_uri = nautilus_file_get_uri (source); + } + parent_uri = fm_directory_view_get_backing_uri (directory_view); + nautilus_file_operations_new_file (GTK_WIDGET (directory_view), + parent_uri, + source_uri, + new_folder_done, data); + g_free (parent_uri); + g_free (source_uri); +} + + /* handle the open command */ static void @@ -3337,6 +3617,10 @@ add_numbered_menu_item (BonoboUIComponent *ui, GDestroyNotify destroy_notify) { char *escaped_parent_path, *escaped_label, *verb_name, *item_path; + + if (parent_path == NULL) { + return; + } escaped_parent_path = eel_str_double_underscores (parent_path); @@ -3372,11 +3656,13 @@ add_submenu (BonoboUIComponent *ui, { char *escaped_parent_path, *escaped_label; - escaped_parent_path = eel_str_double_underscores (parent_path); - escaped_label = eel_str_double_underscores (label); - nautilus_bonobo_add_submenu (ui, escaped_parent_path, escaped_label, pixbuf); - g_free (escaped_label); - g_free (escaped_parent_path); + if (parent_path != NULL) { + escaped_parent_path = eel_str_double_underscores (parent_path); + escaped_label = eel_str_double_underscores (label); + nautilus_bonobo_add_submenu (ui, escaped_parent_path, escaped_label, pixbuf); + g_free (escaped_label); + g_free (escaped_parent_path); + } } static void @@ -3878,12 +4164,12 @@ run_script_callback (BonoboUIComponent *component, gpointer callback_data, const } static void -add_script_to_script_menus (FMDirectoryView *directory_view, - NautilusFile *file, - int index, - const char *menu_path, - const char *popup_path, - const char *popup_bg_path) +add_script_to_scripts_menus (FMDirectoryView *directory_view, + NautilusFile *file, + int index, + const char *menu_path, + const char *popup_path, + const char *popup_bg_path) { ScriptLaunchParameters *launch_parameters; char *tip; @@ -3935,11 +4221,11 @@ add_script_to_script_menus (FMDirectoryView *directory_view, } static void -add_submenu_to_script_menus (FMDirectoryView *directory_view, - NautilusFile *file, - const char *menu_path, - const char *popup_path, - const char *popup_bg_path) +add_submenu_to_directory_menus (FMDirectoryView *directory_view, + NautilusFile *file, + const char *menu_path, + const char *popup_path, + const char *popup_bg_path) { char *name; GdkPixbuf *pixbuf; @@ -3987,19 +4273,22 @@ update_directory_in_scripts_menu (FMDirectoryView *view, NautilusDirectory *dire NautilusFile *file; NautilusDirectory *dir; char *uri; + char *escaped_path; int i; uri = nautilus_directory_get_uri (directory); + escaped_path = gnome_vfs_escape_path_string (uri + scripts_directory_uri_length); + g_free (uri); menu_path = g_strconcat (FM_DIRECTORY_VIEW_MENU_PATH_SCRIPTS_PLACEHOLDER, - uri + scripts_directory_uri_length, + escaped_path, NULL); popup_path = g_strconcat (FM_DIRECTORY_VIEW_POPUP_PATH_SCRIPTS_PLACEHOLDER, - uri + scripts_directory_uri_length, + escaped_path, NULL); popup_bg_path = g_strconcat (FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_SCRIPTS_PLACEHOLDER, - uri + scripts_directory_uri_length, + escaped_path, NULL); - g_free (uri); + g_free (escaped_path); file_list = nautilus_directory_get_file_list (directory); filtered = nautilus_file_list_filter_hidden_and_backup (file_list, FALSE, FALSE); @@ -4013,7 +4302,7 @@ update_directory_in_scripts_menu (FMDirectoryView *view, NautilusDirectory *dire file = node->data; if (file_is_launchable (file)) { - add_script_to_script_menus (view, file, i++, menu_path, popup_path, popup_bg_path); + add_script_to_scripts_menus (view, file, i++, menu_path, popup_path, popup_bg_path); any_scripts = TRUE; } else if (nautilus_file_is_directory (file)) { uri = nautilus_file_get_uri (file); @@ -4022,7 +4311,7 @@ update_directory_in_scripts_menu (FMDirectoryView *view, NautilusDirectory *dire add_directory_to_scripts_directory_list (view, dir); nautilus_directory_unref (dir); - add_submenu_to_script_menus (view, file, menu_path, popup_path, popup_bg_path); + add_submenu_to_directory_menus (view, file, menu_path, popup_path, popup_bg_path); any_scripts = TRUE; } @@ -4097,6 +4386,204 @@ update_scripts_menu (FMDirectoryView *view) } static void +create_template_callback (BonoboUIComponent *component, gpointer callback_data, const char *path) +{ + CreateTemplateParameters *parameters; + + parameters = callback_data; + + fm_directory_view_new_file (parameters->directory_view, parameters->file); +} + + +static void +add_template_to_templates_menus (FMDirectoryView *directory_view, + NautilusFile *file, + int index, + const char *menu_path, + const char *popup_bg_path) +{ + char *tip; + char *name; + char *dot; + GdkPixbuf *pixbuf; + CreateTemplateParameters *parameters; + + name = nautilus_file_get_display_name (file); + + tip = g_strdup_printf (_("Create Document from template \"%s\""), name); + + /* Remove extension */ + dot = strrchr (name, '.'); + if (dot != NULL) { + *dot = 0; + } + + pixbuf = nautilus_icon_factory_get_pixbuf_for_file + (file, NULL, NAUTILUS_ICON_SIZE_FOR_MENUS); + + parameters = create_template_parameters_new (file, directory_view); + add_numbered_menu_item (directory_view->details->ui, + menu_path, + name, + tip, + index, + pixbuf, + create_template_callback, + parameters, + (GDestroyNotify) create_templates_parameters_free); + + /* Use same uri and no DestroyNotify for popup item, which has same + * lifetime as the item in the File menu in the menu bar. + */ + add_numbered_menu_item (directory_view->details->ui, + popup_bg_path, + name, + tip, + index, + pixbuf, + create_template_callback, + parameters, + NULL); + + g_object_unref (pixbuf); + g_free (name); + g_free (tip); +} + + +static gboolean +directory_belongs_in_templates_menu (const char *uri) +{ + int num_levels; + int i; + + if (!eel_str_has_prefix (uri, templates_directory_uri)) { + return FALSE; + } + + num_levels = 0; + for (i = templates_directory_uri_length; uri[i] != '\0'; i++) { + if (uri[i] == '/') { + num_levels++; + } + } + + if (num_levels > MAX_MENU_LEVELS) { + return FALSE; + } + + return TRUE; +} + +static gboolean +update_directory_in_templates_menu (FMDirectoryView *view, NautilusDirectory *directory) +{ + char *menu_path, *popup_bg_path; + GList *file_list, *filtered, *node; + gboolean any_templates; + NautilusFile *file; + NautilusDirectory *dir; + char *escaped_path; + char *uri; + int i; + + uri = nautilus_directory_get_uri (directory); + escaped_path = gnome_vfs_escape_path_string (uri + templates_directory_uri_length); + g_free (uri); + menu_path = g_strconcat (FM_DIRECTORY_VIEW_MENU_PATH_NEW_DOCUMENTS_PLACEHOLDER, + escaped_path, + NULL); + popup_bg_path = g_strconcat (FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_NEW_DOCUMENTS_PLACEHOLDER, + escaped_path, + NULL);; + + g_free (escaped_path); + + file_list = nautilus_directory_get_file_list (directory); + filtered = nautilus_file_list_filter_hidden_and_backup (file_list, FALSE, FALSE); + nautilus_file_list_free (file_list); + + file_list = nautilus_file_list_sort_by_display_name (filtered); + + any_templates = FALSE; + i = 0; + for (node = file_list; node != NULL; node = node->next) { + file = node->data; + + if (nautilus_file_is_directory (file)) { + uri = nautilus_file_get_uri (file); + if (directory_belongs_in_templates_menu (uri)) { + dir = nautilus_directory_get (uri); + add_directory_to_templates_directory_list (view, dir); + nautilus_directory_unref (dir); + + add_submenu_to_directory_menus (view, file, menu_path, NULL, popup_bg_path); + + any_templates = TRUE; + } + g_free (uri); + } else if (nautilus_file_can_read (file)) { + add_template_to_templates_menus (view, file, i++, menu_path, popup_bg_path); + any_templates = TRUE; + } + } + + nautilus_file_list_free (file_list); + + g_free (popup_bg_path); + g_free (menu_path); + + return any_templates; +} + + + +static void +update_templates_menu (FMDirectoryView *view) +{ + gboolean any_templates; + GList *sorted_copy, *node; + NautilusDirectory *directory; + char *uri; + + /* There is a race condition here. If we don't mark the scripts menu as + valid before we begin our task then we can lose template menu updates that + occur before we finish. */ + view->details->templates_invalid = FALSE; + + nautilus_bonobo_remove_menu_items_and_commands + (view->details->ui, FM_DIRECTORY_VIEW_MENU_PATH_NEW_DOCUMENTS_PLACEHOLDER); + nautilus_bonobo_remove_menu_items_and_commands + (view->details->ui, FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_NEW_DOCUMENTS_PLACEHOLDER); + + /* As we walk through the directories, remove any that no longer belong. */ + any_templates = FALSE; + sorted_copy = nautilus_directory_list_sort_by_uri + (nautilus_directory_list_copy (view->details->templates_directory_list)); + for (node = sorted_copy; node != NULL; node = node->next) { + directory = node->data; + + uri = nautilus_directory_get_uri (directory); + if (!directory_belongs_in_templates_menu (uri)) { + remove_directory_from_templates_directory_list (view, directory); + } else if (update_directory_in_templates_menu (view, directory)) { + any_templates = TRUE; + } + g_free (uri); + } + nautilus_directory_list_free (sorted_copy); + + nautilus_bonobo_set_hidden (view->details->ui, + FM_DIRECTORY_VIEW_MENU_PATH_NEW_DOCUMENTS_NO_TEMPLATES, + any_templates); + nautilus_bonobo_set_hidden (view->details->ui, + FM_DIRECTORY_VIEW_POPUP_PATH_BACKGROUND_NEW_DOCUMENTS_NO_TEMPLATES, + any_templates); +} + + +static void open_scripts_folder_callback (BonoboUIComponent *component, gpointer callback_data, const char *verb) @@ -4109,7 +4596,8 @@ open_scripts_folder_callback (BonoboUIComponent *component, eel_show_info_dialog_with_details (_("All executable files in this folder will appear in the " - "Scripts menu. Choosing a script from the menu will run " + "Scripts menu."), + _("Choosing a script from the menu will run " "that script with any selected items as input."), _("About Scripts"), _("All executable files in this folder will appear in the " @@ -4437,7 +4925,8 @@ drive_mounted_callback (gboolean succeeded, gpointer data) { if (!succeeded) { - eel_show_error_dialog_with_details (error, _("Mount Error"), detailed_error, NULL); + eel_show_error_dialog_with_details (error, NULL, + _("Mount Error"), detailed_error, NULL); } } @@ -4498,9 +4987,11 @@ volume_or_drive_unmounted_callback (gboolean succeeded, eject = GPOINTER_TO_INT (data); if (!succeeded) { if (eject) { - eel_show_error_dialog_with_details (error, _("Unmount Error"), detailed_error, NULL); + eel_show_error_dialog_with_details (error, NULL, + _("Unmount Error"), detailed_error, NULL); } else { - eel_show_error_dialog_with_details (error, _("Eject Error"), detailed_error, NULL); + eel_show_error_dialog_with_details (error, NULL, + _("Eject Error"), detailed_error, NULL); } } } @@ -4564,9 +5055,11 @@ real_merge_menus (FMDirectoryView *view) BONOBO_UI_VERB ("Duplicate", duplicate_callback), BONOBO_UI_VERB ("Empty Trash", bonobo_menu_empty_trash_callback), BONOBO_UI_VERB ("New Folder", new_folder_callback), + BONOBO_UI_VERB ("New Empty File", new_empty_file_callback), BONOBO_UI_VERB ("New Launcher", new_launcher_callback), BONOBO_UI_VERB ("Open Scripts Folder", open_scripts_folder_callback), BONOBO_UI_VERB ("Open", open_callback), + BONOBO_UI_VERB ("OpenCloseParent", open_close_parent_callback), BONOBO_UI_VERB ("OpenAlternate", open_alternate_callback), BONOBO_UI_VERB ("OtherApplication", other_application_callback), BONOBO_UI_VERB ("OtherViewer", other_viewer_callback), @@ -4603,6 +5096,7 @@ real_merge_menus (FMDirectoryView *view) } view->details->scripts_invalid = TRUE; + view->details->templates_invalid = TRUE; } static void @@ -4977,6 +5471,9 @@ real_update_menus (FMDirectoryView *view) if (view->details->scripts_invalid) { update_scripts_menu (view); } + if (view->details->templates_invalid) { + update_templates_menu (view); + } } /** @@ -5051,6 +5548,47 @@ schedule_update_menus (FMDirectoryView *view) } } +static void +remove_update_status_idle_callback (FMDirectoryView *view) +{ + if (view->details->update_status_idle_id != 0) { + g_source_remove (view->details->update_status_idle_id); + view->details->update_status_idle_id = 0; + } +} + +static gboolean +update_status_idle_callback (gpointer data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); + fm_directory_view_display_selection_info (view); + view->details->update_status_idle_id = 0; + return FALSE; +} + +static void +schedule_update_status (FMDirectoryView *view) +{ + g_assert (FM_IS_DIRECTORY_VIEW (view)); + + /* Make sure we haven't already destroyed it */ + g_assert (view->details->nautilus_view != NULL); + + if (view->details->loading) { + /* Don't update status bar while loading the dir */ + return; + } + + if (view->details->update_status_idle_id == 0) { + view->details->update_status_idle_id = + g_idle_add_full (G_PRIORITY_DEFAULT_IDLE - 20, + update_status_idle_callback, view, NULL); + } +} + + /** * fm_directory_view_notify_selection_changed: * @@ -5110,6 +5648,7 @@ report_broken_symbolic_link (FMDirectoryView *view, NautilusFile *file) { char *target_path; char *prompt; + char *detail; GtkDialog *dialog; GList file_as_list; int response; @@ -5117,17 +5656,16 @@ report_broken_symbolic_link (FMDirectoryView *view, NautilusFile *file) g_assert (nautilus_file_is_broken_symbolic_link (file)); target_path = nautilus_file_get_symbolic_link_target_path (file); + prompt = _("The link is borken, do you want to move it to the Trash?"); if (target_path == NULL) { - prompt = g_strdup_printf (_("This link can't be used, because it has no target. " - "Do you want to move this link to the Trash?")); + detail = g_strdup (_("This link can't be used, because it has no target.")); } else { - prompt = g_strdup_printf (_("This link can't be used, because its target \"%s\" doesn't exist. " - "Do you want to move this link to the Trash?"), - target_path); + detail = g_strdup_printf (_("This link can't be used, because its target " + "\"%s\" doesn't exist."), target_path); } dialog = eel_show_yes_no_dialog (prompt, - _("Broken Link"), _("Mo_ve to Trash"), GTK_STOCK_CANCEL, + detail, _("Broken Link"), _("Mo_ve to Trash"), GTK_STOCK_CANCEL, fm_directory_view_get_containing_window (view)); gtk_dialog_set_default_response (dialog, GTK_RESPONSE_YES); @@ -5152,7 +5690,7 @@ report_broken_symbolic_link (FMDirectoryView *view, NautilusFile *file) } g_free (target_path); - g_free (prompt); + g_free (detail); } static ActivationAction @@ -5161,6 +5699,7 @@ get_executable_text_file_action (FMDirectoryView *view, NautilusFile *file) GtkDialog *dialog; char *file_name; char *prompt; + char *detail; int preferences_value; int response; @@ -5184,21 +5723,25 @@ get_executable_text_file_action (FMDirectoryView *view, NautilusFile *file) file_name = nautilus_file_get_display_name (file); - prompt = g_strdup_printf (_("\"%s\" is an executable text file. " - "Do you want to run it, or display its contents?"), - file_name); + prompt = g_strdup_printf (_("Do you want to run \"%s\", or display its contents?"), + file_name); + detail = g_strdup_printf (_("\"%s\" is an executable text file."), + file_name); g_free (file_name); dialog = eel_create_question_dialog (prompt, + detail, _("Run or Display?"), - _("_Display"), RESPONSE_DISPLAY, _("Run in _Terminal"), RESPONSE_RUN_IN_TERMINAL, + _("_Display"), RESPONSE_DISPLAY, fm_directory_view_get_containing_window (view)); gtk_dialog_add_button (dialog, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL); gtk_dialog_add_button (dialog, _("_Run"), RESPONSE_RUN); + gtk_dialog_set_default_response (dialog, GTK_RESPONSE_CANCEL); gtk_widget_show (GTK_WIDGET (dialog)); g_free (prompt); + g_free (detail); response = gtk_dialog_run (dialog); gtk_object_destroy (GTK_OBJECT (dialog)); @@ -5292,9 +5835,13 @@ activate_callback (NautilusFile *file, gpointer callback_data) nautilus_launch_show_file (file, fm_directory_view_get_containing_window (view)); - file_uri = nautilus_file_get_uri (file); - egg_recent_model_add (nautilus_recent_get_model (), file_uri); - g_free (file_uri); + /* We should not add trash and directory uris.*/ + if ((!nautilus_file_is_in_trash (file)) && + (!nautilus_file_is_directory (file))) { + file_uri = nautilus_file_get_uri (file); + egg_recent_model_add (nautilus_recent_get_model (), file_uri); + g_free (file_uri); + } } } @@ -5323,7 +5870,10 @@ activation_drive_mounted_callback (gboolean succeeded, } else { if (!parameters->cancelled) { eel_timed_wait_stop (cancel_activate_callback, parameters); - eel_show_error_dialog_with_details (error, _("Mount Error"), detailed_error, NULL); + eel_show_error_dialog_with_details (error, NULL, + _("Mount Error"), + detailed_error, + NULL); } nautilus_file_unref (parameters->file); @@ -5454,7 +6004,7 @@ fm_directory_view_activate_file (FMDirectoryView *view, parameters->cancelled = FALSE; file_name = nautilus_file_get_display_name (file); - timed_wait_prompt = g_strdup_printf (_("Opening \"%s\""), file_name); + timed_wait_prompt = g_strdup_printf (_("Opening \"%s\"."), file_name); g_free (file_name); eel_timed_wait_start @@ -5523,6 +6073,7 @@ file_changed_callback (NautilusFile *file, gpointer callback_data) FMDirectoryView *view = FM_DIRECTORY_VIEW (callback_data); schedule_update_menus (view); + schedule_update_status (view); /* We might have different capabilities, so we need to update relative icon emblems . (Writeable etc) */ @@ -6429,6 +6980,7 @@ fm_directory_view_class_init (FMDirectoryViewClass *klass) EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, file_changed); EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_background_widget); EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_selection); + EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, get_item_count); EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, is_empty); EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, reset_to_defaults); EEL_ASSIGN_MUST_OVERRIDE_SIGNAL (klass, fm_directory_view, restore_default_zoom_level); diff --git a/src/file-manager/fm-directory-view.h b/src/file-manager/fm-directory-view.h index 2fa6afaef..6f138e9bb 100644 --- a/src/file-manager/fm-directory-view.h +++ b/src/file-manager/fm-directory-view.h @@ -152,6 +152,8 @@ struct FMDirectoryViewClass { /* Return an array of locations of selected icons in their view. */ GArray * (* get_selected_icon_locations) (FMDirectoryView *view); + guint (* get_item_count) (FMDirectoryView *view); + /* bump_zoom_level is a function pointer that subclasses must override * to change the zoom level of an object. */ void (* bump_zoom_level) (FMDirectoryView *view, @@ -299,6 +301,7 @@ gboolean fm_directory_view_can_accept_item (Nautilus void fm_directory_view_display_selection_info (FMDirectoryView *view); GList * fm_directory_view_get_selection (FMDirectoryView *view); void fm_directory_view_stop (FMDirectoryView *view); +guint fm_directory_view_get_item_count (FMDirectoryView *view); gboolean fm_directory_view_can_zoom_in (FMDirectoryView *view); gboolean fm_directory_view_can_zoom_out (FMDirectoryView *view); GtkWidget * fm_directory_view_get_background_widget (FMDirectoryView *view); @@ -370,6 +373,8 @@ gboolean fm_directory_view_should_show_file (FMDirect gboolean fm_directory_view_should_sort_directories_first (FMDirectoryView *view); void fm_directory_view_update_menus (FMDirectoryView *view); void fm_directory_view_new_folder (FMDirectoryView *view); +void fm_directory_view_new_file (FMDirectoryView *view, + NautilusFile *source); void fm_directory_view_ignore_hidden_file_preferences (FMDirectoryView *view); #endif /* FM_DIRECTORY_VIEW_H */ diff --git a/src/file-manager/fm-error-reporting.c b/src/file-manager/fm-error-reporting.c index 48167add6..09d22c33e 100644 --- a/src/file-manager/fm-error-reporting.c +++ b/src/file-manager/fm-error-reporting.c @@ -68,7 +68,8 @@ fm_report_error_loading_directory (NautilusFile *file, message = g_strdup_printf (_("Sorry, couldn't display all the contents of \"%s\"."), file_name); } - eel_show_error_dialog (message, _("Error Displaying Folder"), parent_window); + eel_show_error_dialog (_("The folder contents could not be displayed."), message, + _("Error Displaying Folder"), parent_window); g_free (file_name); g_free (message); @@ -140,7 +141,8 @@ fm_report_error_renaming_file (NautilusFile *file, g_free (original_name_truncated); g_free (new_name_truncated); - eel_show_error_dialog (message, _("Renaming Error"), parent_window); + eel_show_error_dialog (_("The item could not be renamed."), message, + _("Renaming Error"), parent_window); g_free (message); } @@ -176,7 +178,7 @@ fm_report_error_setting_group (NautilusFile *file, g_free (file_name); } - eel_show_error_dialog (message, _("Error Setting Group"), parent_window); + eel_show_error_dialog (_("The group could not be changed."), message , _("Error Setting Group"), parent_window); g_free (file_name); g_free (message); @@ -208,7 +210,7 @@ fm_report_error_setting_owner (NautilusFile *file, message = g_strdup_printf (_("Sorry, couldn't change the owner of \"%s\"."), file_name); } - eel_show_error_dialog (message, _("Error Setting Owner"), parent_window); + eel_show_error_dialog (_("The owner could not be changed."), message, _("Error Setting Owner"), parent_window); g_free (file_name); g_free (message); @@ -240,7 +242,8 @@ fm_report_error_setting_permissions (NautilusFile *file, message = g_strdup_printf (_("Sorry, couldn't change the permissions of \"%s\"."), file_name); } - eel_show_error_dialog (message, _("Error Setting Permissions"), parent_window); + eel_show_error_dialog (_("The permissions could not be changed."), message, + _("Error Setting Permissions"), parent_window); g_free (file_name); g_free (message); diff --git a/src/file-manager/fm-icon-container.c b/src/file-manager/fm-icon-container.c index 6cffbaabe..b84509898 100644 --- a/src/file-manager/fm-icon-container.c +++ b/src/file-manager/fm-icon-container.c @@ -24,6 +24,8 @@ #include <config.h> #include <libgnome/gnome-macros.h> +#include <libgnome/gnome-i18n.h> +#include <libgnomevfs/gnome-vfs-mime-handlers.h> #include <libgnomevfs/gnome-vfs-ops.h> #include <libgnomevfs/gnome-vfs-uri.h> #include <libgnomevfs/gnome-vfs-utils.h> @@ -54,7 +56,8 @@ fm_icon_container_get_icon_images (NautilusIconContainer *container, NautilusIconData *data, GList **emblem_icons, char **embedded_text, - gboolean *embedded_text_needs_loading) + gboolean *embedded_text_needs_loading, + gboolean *has_window_open) { FMIconView *icon_view; EelStringList *emblems_to_ignore; @@ -78,9 +81,32 @@ fm_icon_container_get_icon_images (NautilusIconContainer *container, eel_string_list_free (emblems_to_ignore); } + *has_window_open = nautilus_file_has_open_window (file); + return nautilus_icon_factory_get_icon_for_file (file, TRUE); } +static char * +fm_icon_container_get_icon_description (NautilusIconContainer *container, + NautilusIconData *data) +{ + NautilusFile *file; + char *mime_type; + const char *description; + + file = NAUTILUS_FILE (data); + g_assert (NAUTILUS_IS_FILE (file)); + + if (NAUTILUS_IS_DESKTOP_ICON_FILE (file)) { + return NULL; + } + + mime_type = nautilus_file_get_mime_type (file); + description = gnome_vfs_mime_get_description (mime_type); + g_free (mime_type); + return g_strdup (description); +} + static void fm_icon_container_start_monitor_top_left (NautilusIconContainer *container, NautilusIconData *data, @@ -445,6 +471,7 @@ fm_icon_container_class_init (FMIconContainerClass *klass) ic_class->get_icon_text = fm_icon_container_get_icon_text; ic_class->get_icon_images = fm_icon_container_get_icon_images; + ic_class->get_icon_description = fm_icon_container_get_icon_description; ic_class->start_monitor_top_left = fm_icon_container_start_monitor_top_left; ic_class->stop_monitor_top_left = fm_icon_container_stop_monitor_top_left; ic_class->prioritize_thumbnailing = fm_icon_container_prioritize_thumbnailing; @@ -463,9 +490,13 @@ fm_icon_container_instance_init (FMIconContainer *icon_container) NautilusIconContainer * fm_icon_container_construct (FMIconContainer *icon_container, FMIconView *view) { + AtkObject *atk_obj; + g_return_val_if_fail (FM_IS_ICON_VIEW (view), NULL); icon_container->view = view; + atk_obj = gtk_widget_get_accessible (GTK_WIDGET (icon_container)); + atk_object_set_name (atk_obj, _("Icon View")); return NAUTILUS_ICON_CONTAINER (icon_container); } diff --git a/src/file-manager/fm-icon-view.c b/src/file-manager/fm-icon-view.c index f2e66989c..3ee68925c 100644 --- a/src/file-manager/fm-icon-view.c +++ b/src/file-manager/fm-icon-view.c @@ -1358,6 +1358,33 @@ fm_icon_view_get_selection (FMDirectoryView *view) } static void +count_item (NautilusIconData *icon_data, + gpointer callback_data) +{ + guint *count; + + count = callback_data; + (*count)++; +} + +static guint +fm_icon_view_get_item_count (FMDirectoryView *view) +{ + guint count; + + g_return_val_if_fail (FM_IS_ICON_VIEW (view), 0); + + count = 0; + + nautilus_icon_container_for_each + (get_icon_container (FM_ICON_VIEW (view)), + count_item, &count); + + return count; +} + + +static void set_sort_criterion_by_id (FMIconView *icon_view, const char *id) { const SortCriterion *sort; @@ -1771,8 +1798,9 @@ play_file (gpointer callback_data) file_uri = nautilus_file_get_uri (file); mime_type = nautilus_file_get_mime_type (file); is_mp3 = eel_strcasecmp (mime_type, "audio/mpeg") == 0; - is_ogg = eel_strcasecmp (mime_type, "application/x-ogg") == 0; - + is_ogg = eel_strcasecmp (mime_type, "application/x-ogg") == 0 || + eel_strcasecmp (mime_type, "application/ogg") == 0; + mp3_pid = fork (); if (mp3_pid == (pid_t) 0) { /* Set the group (session) id to this process for future killing. */ @@ -2541,8 +2569,9 @@ icon_view_handle_uri_list (NautilusIconContainer *container, const char *item_ur if (eel_vfs_has_capability (container_uri, EEL_VFS_CAPABILITY_IS_REMOTE_AND_SLOW)) { - eel_show_warning_dialog (_("Drag and drop is only supported to local file systems."), - _("Drag and Drop error"), + eel_show_warning_dialog (_("Drag and drop is not supported."), + _("Drag and drop is only supported on local file systems."), + _("Drag and Drop Error"), fm_directory_view_get_containing_window (FM_DIRECTORY_VIEW (view))); g_free (container_uri); return; @@ -2564,8 +2593,9 @@ icon_view_handle_uri_list (NautilusIconContainer *container, const char *item_ur (action != GDK_ACTION_COPY) && (action != GDK_ACTION_MOVE) && (action != GDK_ACTION_LINK)) { - eel_show_warning_dialog (_("An invalid drag type was used."), - _("Drag and Drop error"), + eel_show_warning_dialog (_("Drag and drop is not supported."), + _("An invalid drag type was used."), + _("Drag and Drop Error"), fm_directory_view_get_containing_window (FM_DIRECTORY_VIEW (view))); g_free (container_uri); return; @@ -2747,6 +2777,7 @@ fm_icon_view_class_init (FMIconViewClass *klass) fm_directory_view_class->get_background_widget = fm_icon_view_get_background_widget; fm_directory_view_class->get_selected_icon_locations = fm_icon_view_get_selected_icon_locations; fm_directory_view_class->get_selection = fm_icon_view_get_selection; + fm_directory_view_class->get_item_count = fm_icon_view_get_item_count; fm_directory_view_class->is_empty = fm_icon_view_is_empty; fm_directory_view_class->remove_file = fm_icon_view_remove_file; fm_directory_view_class->reset_to_defaults = fm_icon_view_reset_to_defaults; diff --git a/src/file-manager/fm-list-model.c b/src/file-manager/fm-list-model.c index ad3c746f9..48558537d 100644 --- a/src/file-manager/fm-list-model.c +++ b/src/file-manager/fm-list-model.c @@ -189,6 +189,7 @@ fm_list_model_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter, int column int icon_size; NautilusZoomLevel zoom_level; int width, height; + char *modifier; model = (FMListModel *)tree_model; @@ -220,7 +221,31 @@ fm_list_model_get_value (GtkTreeModel *tree_model, GtkTreeIter *iter, int column zoom_level = fm_list_model_get_zoom_level_from_column_id (column); icon_size = nautilus_get_icon_size_for_zoom_level (zoom_level); - icon = nautilus_icon_factory_get_pixbuf_for_file (file, NULL, icon_size); + + modifier = NULL; + if (model->details->drag_view != NULL) { + GtkTreePath *path_a, *path_b; + + gtk_tree_view_get_drag_dest_row (model->details->drag_view, + &path_a, + NULL); + if (path_a != NULL) { + path_b = gtk_tree_model_get_path (tree_model, iter); + + if (gtk_tree_path_compare (path_a, path_b) == 0) { + modifier = "visiting"; + } + + gtk_tree_path_free (path_a); + gtk_tree_path_free (path_b); + } + } + + if (nautilus_file_has_open_window (file)) { + modifier = "accept"; + } + + icon = nautilus_icon_factory_get_pixbuf_for_file (file, modifier, icon_size); height = gdk_pixbuf_get_height (icon); if (height > icon_size) { @@ -649,6 +674,13 @@ fm_list_model_is_empty (FMListModel *model) return (g_sequence_get_length (model->details->files) == 0); } +guint +fm_list_model_get_length (FMListModel *model) +{ + return g_sequence_get_length (model->details->files); +} + + static void fm_list_model_remove (FMListModel *model, GtkTreeIter *iter) { @@ -691,6 +723,23 @@ fm_list_model_clear (FMListModel *model) } } +NautilusFile * +fm_list_model_file_for_path (FMListModel *model, GtkTreePath *path) +{ + NautilusFile *file; + GtkTreeIter iter; + + file = NULL; + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (model), + &iter, path)) { + gtk_tree_model_get (GTK_TREE_MODEL (model), + &iter, + FM_LIST_MODEL_FILE_COLUMN, &file, + -1); + } + return file; +} + void fm_list_model_set_should_sort_directories_first (FMListModel *model, gboolean sort_directories_first) { diff --git a/src/file-manager/fm-list-model.h b/src/file-manager/fm-list-model.h index 7c6d846ef..dcf3b4c09 100644 --- a/src/file-manager/fm-list-model.h +++ b/src/file-manager/fm-list-model.h @@ -71,6 +71,7 @@ void fm_list_model_add_file (FMListModel * void fm_list_model_file_changed (FMListModel *model, NautilusFile *file); gboolean fm_list_model_is_empty (FMListModel *model); +guint fm_list_model_get_length (FMListModel *model); void fm_list_model_remove_file (FMListModel *model, NautilusFile *file); void fm_list_model_clear (FMListModel *model); diff --git a/src/file-manager/fm-list-view.c b/src/file-manager/fm-list-view.c index fcfd774fb..6d0355646 100644 --- a/src/file-manager/fm-list-view.c +++ b/src/file-manager/fm-list-view.c @@ -80,6 +80,7 @@ struct FMListViewDetails { int drag_y; gboolean drag_started; + gboolean row_selected_on_button_down; }; /* @@ -161,11 +162,18 @@ activate_selected_items (FMListView *view) } static void -activate_selected_items_alternate (FMListView *view) +activate_selected_items_alternate (FMListView *view, + NautilusFile *file) { GList *file_list; - - file_list = fm_list_view_get_selection (FM_DIRECTORY_VIEW (view)); + + + if (file != NULL) { + nautilus_file_ref (file); + file_list = g_list_prepend (NULL, file); + } else { + file_list = fm_list_view_get_selection (FM_DIRECTORY_VIEW (view)); + } fm_directory_view_activate_files (FM_DIRECTORY_VIEW (view), file_list, Nautilus_ViewFrame_OPEN_ACCORDING_TO_MODE, @@ -193,12 +201,17 @@ fm_list_view_did_not_drag (FMListView *view, if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, &path, NULL, NULL, NULL)) { - if((event->button == 1 || event->button == 2) - && (click_policy_auto_value == NAUTILUS_CLICK_POLICY_DOUBLE) - && gtk_tree_selection_path_is_selected (selection, path) - && !button_event_modifies_selection (event)) { - gtk_tree_selection_unselect_all (selection); - gtk_tree_selection_select_path (selection, path); + if ((event->button == 1 || event->button == 2) + && ((event->state & GDK_CONTROL_MASK) != 0 || + (event->state & GDK_SHIFT_MASK) == 0) + && view->details->row_selected_on_button_down + && (click_policy_auto_value == NAUTILUS_CLICK_POLICY_DOUBLE)) { + if (!button_event_modifies_selection (event)) { + gtk_tree_selection_unselect_all (selection); + gtk_tree_selection_select_path (selection, path); + } else { + gtk_tree_selection_unselect_path (selection, path); + } } if ((click_policy_auto_value == NAUTILUS_CLICK_POLICY_SINGLE) @@ -206,7 +219,7 @@ fm_list_view_did_not_drag (FMListView *view, if (event->button == 1) { activate_selected_items (view); } else if (event->button == 2) { - activate_selected_items_alternate (view); + activate_selected_items_alternate (view, NULL); } } gtk_tree_path_free (path); @@ -436,6 +449,7 @@ button_press_callback (GtkWidget *widget, GdkEventButton *event, gpointer callba return TRUE; } + call_parent = TRUE; allow_drag = FALSE; if (gtk_tree_view_get_path_at_pos (tree_view, event->x, event->y, @@ -448,15 +462,26 @@ button_press_callback (GtkWidget *widget, GdkEventButton *event, gpointer callba view->details->double_click_path[1] = view->details->double_click_path[0]; view->details->double_click_path[0] = gtk_tree_path_copy (path); } - if (event->type == GDK_2BUTTON_PRESS) { + /* Double clicking does not trigger a D&D action. */ + view->details->drag_button = 0; + call_parent = TRUE; if (view->details->double_click_path[1] && - gtk_tree_path_compare (view->details->double_click_path[0], view->details->double_click_path[1]) == 0 - && !button_event_modifies_selection (event)) { - if ((event->button == 1 || event->button == 3)) { - activate_selected_items (view); - } else if (event->button == 2) { - activate_selected_items_alternate (view); + gtk_tree_path_compare (view->details->double_click_path[0], view->details->double_click_path[1]) == 0) { + if (!button_event_modifies_selection (event)) { + if ((event->button == 1 || event->button == 3)) { + activate_selected_items (view); + } else if (event->button == 2) { + activate_selected_items_alternate (view, NULL); + } + } else if (event->button == 1 && + (event->state & GDK_SHIFT_MASK) != 0) { + NautilusFile *file; + file = fm_list_model_file_for_path (view->details->model, path); + if (file != NULL) { + activate_selected_items_alternate (view, file); + nautilus_file_unref (file); + } } } } @@ -471,17 +496,26 @@ button_press_callback (GtkWidget *widget, GdkEventButton *event, gpointer callba call_parent = FALSE; } - if(!button_event_modifies_selection (event) && - (event->button == 1 || event->button == 2) && - gtk_tree_selection_path_is_selected (selection, path)) { - call_parent = FALSE; + if ((event->button == 1 || event->button == 2) && + ((event->state & GDK_CONTROL_MASK) != 0 || + (event->state & GDK_SHIFT_MASK) == 0)) { + view->details->row_selected_on_button_down = gtk_tree_selection_path_is_selected (selection, path); + if (view->details->row_selected_on_button_down) { + call_parent = FALSE; + } else if ((event->state & GDK_CONTROL_MASK) != 0) { + call_parent = FALSE; + gtk_tree_selection_select_path (selection, path); + } } if (call_parent) { tree_view_class->button_press_event (widget, event); + } else if (gtk_tree_selection_path_is_selected (selection, path)) { + gtk_widget_grab_focus (widget); } - if (event->button == 1 || event->button == 2) { + if ((event->button == 1 || event->button == 2) && + event->type == GDK_BUTTON_PRESS) { view->details->drag_started = FALSE; view->details->drag_button = event->button; view->details->drag_x = event->x; @@ -494,9 +528,21 @@ button_press_callback (GtkWidget *widget, GdkEventButton *event, gpointer callba gtk_tree_path_free (path); } else { + if ((event->button == 1 || event->button == 2) && + event->type == GDK_BUTTON_PRESS) { + if (view->details->double_click_path[1]) { + gtk_tree_path_free (view->details->double_click_path[1]); + } + view->details->double_click_path[1] = view->details->double_click_path[0]; + view->details->double_click_path[0] = NULL; + } /* Deselect if people click outside any row. It's OK to let default code run; it won't reselect anything. */ gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (tree_view)); + + if (event->button == 3) { + do_popup_menu (widget, view, event); + } } /* We chained to the default handler in this method, so never @@ -701,6 +747,7 @@ create_and_set_up_tree_view (FMListView *view) GtkCellRenderer *cell; GtkTreeViewColumn *column; GtkTargetEntry *drag_types; + AtkObject *atk_obj; int num_drag_types; view->details->tree_view = GTK_TREE_VIEW (gtk_tree_view_new ()); @@ -742,6 +789,9 @@ create_and_set_up_tree_view (FMListView *view) view->details->model = g_object_new (FM_TYPE_LIST_MODEL, NULL); gtk_tree_view_set_model (view->details->tree_view, GTK_TREE_MODEL (view->details->model)); + /* Need the model for the dnd drop icon "accept" change */ + fm_list_model_set_drag_view (FM_LIST_MODEL (view->details->model), + view->details->tree_view, 0, 0); g_signal_connect_object (view->details->model, "sort_column_changed", G_CALLBACK (sort_column_changed_callback), view, 0); @@ -815,6 +865,9 @@ create_and_set_up_tree_view (FMListView *view) gtk_widget_show (GTK_WIDGET (view->details->tree_view)); gtk_container_add (GTK_CONTAINER (view), GTK_WIDGET (view->details->tree_view)); + + atk_obj = gtk_widget_get_accessible (GTK_WIDGET (view->details->tree_view)); + atk_object_set_name (atk_obj, _("List View")); } static void @@ -964,6 +1017,15 @@ fm_list_view_get_selection (FMDirectoryView *view) return list; } + +static guint +fm_list_view_get_item_count (FMDirectoryView *view) +{ + g_return_val_if_fail (FM_IS_LIST_VIEW (view), 0); + + return fm_list_model_get_length (FM_LIST_VIEW (view)->details->model); +} + static gboolean fm_list_view_is_empty (FMDirectoryView *view) { @@ -973,7 +1035,52 @@ fm_list_view_is_empty (FMDirectoryView *view) static void fm_list_view_remove_file (FMDirectoryView *view, NautilusFile *file) { - fm_list_model_remove_file (FM_LIST_VIEW (view)->details->model, file); + GtkTreePath *path; + GtkTreeIter iter; + GtkTreeIter temp_iter; + GtkTreeRowReference* row_reference; + FMListView *list_view; + GtkTreeModel* tree_model; + + path = NULL; + row_reference = NULL; + list_view = FM_LIST_VIEW (view); + tree_model = GTK_TREE_MODEL(list_view->details->model); + + if(fm_list_model_get_tree_iter_from_file (list_view->details->model, file, &iter)) { + temp_iter = iter; + + /* get reference for next element in the list view. If the element to be deleted is the + * last one, get reference to previous element. If there is only one element in view + * no need to select anything. */ + + if(gtk_tree_model_iter_next (tree_model, &iter)) { + path = gtk_tree_model_get_path (tree_model, &iter); + row_reference = gtk_tree_row_reference_new (tree_model, path); + } else { + path = gtk_tree_model_get_path (tree_model, &temp_iter); + if(gtk_tree_path_prev (path)) { + row_reference = gtk_tree_row_reference_new (tree_model, path); + } + } + + gtk_tree_path_free (path); + + fm_list_model_remove_file (list_view->details->model, file); + + if(gtk_tree_row_reference_valid(row_reference)) { + path = gtk_tree_row_reference_get_path (row_reference); + gtk_tree_view_set_cursor (list_view->details->tree_view, path, NULL, FALSE); + gtk_tree_path_free (path); + + } + + if(row_reference) { + gtk_tree_row_reference_free (row_reference); + } + } + + } static void @@ -1432,6 +1539,7 @@ fm_list_view_class_init (FMListViewClass *class) fm_directory_view_class->file_changed = fm_list_view_file_changed; fm_directory_view_class->get_background_widget = fm_list_view_get_background_widget; fm_directory_view_class->get_selection = fm_list_view_get_selection; + fm_directory_view_class->get_item_count = fm_list_view_get_item_count; fm_directory_view_class->is_empty = fm_list_view_is_empty; fm_directory_view_class->remove_file = fm_list_view_remove_file; fm_directory_view_class->reset_to_defaults = fm_list_view_reset_to_defaults; diff --git a/src/file-manager/fm-properties-window.c b/src/file-manager/fm-properties-window.c index 55464fcd2..7af9e5b7c 100644 --- a/src/file-manager/fm-properties-window.c +++ b/src/file-manager/fm-properties-window.c @@ -26,6 +26,7 @@ #include "fm-properties-window.h" #include "fm-error-reporting.h" +#include <eel/eel-accessibility.h> #include <eel/eel-ellipsizing-label.h> #include <eel/eel-gdk-pixbuf-extensions.h> #include <eel/eel-glib-extensions.h> @@ -152,7 +153,7 @@ enum { typedef struct { GList *original_files; GList *target_files; - FMDirectoryView *directory_view; + GtkWidget *parent_widget; char *pending_key; GHashTable *pending_files; } StartupData; @@ -186,7 +187,7 @@ static void is_directory_ready_callback (NautilusFile *file, gpointer data); static void cancel_group_change_callback (gpointer callback_data); static void cancel_owner_change_callback (gpointer callback_data); -static void directory_view_destroyed_callback (FMDirectoryView *view, +static void parent_widget_destroyed_callback (GtkWidget *widget, gpointer callback_data); static void select_image_button_callback (GtkWidget *widget, FMPropertiesWindow *properties_window); @@ -473,8 +474,8 @@ fm_properties_window_drag_data_received (GtkWidget *widget, GdkDragContext *cont if (!exactly_one) { eel_show_error_dialog - (_("You can't assign more than one custom icon at a time! " - "Please drag just one image to set a custom icon."), + (_("You can't assign more than one custom icon at a time!"), + _("Please drag just one image to set a custom icon."), _("More Than One Image"), window); } else { @@ -484,15 +485,15 @@ fm_properties_window_drag_data_received (GtkWidget *widget, GdkDragContext *cont } else { if (eel_is_remote_uri (uris[0])) { eel_show_error_dialog - (_("The file that you dropped is not local. " - "You can only use local images as custom icons."), + (_("The file that you dropped is not local."), + _("You can only use local images as custom icons."), _("Local Images Only"), window); } else { eel_show_error_dialog - (_("The file that you dropped is not an image. " - "You can only use local images as custom icons."), + (_("The file that you dropped is not an image."), + _("You can only use local images as custom icons."), _("Images Only"), window); } @@ -1375,7 +1376,7 @@ activate_group_callback (GtkMenuItem *menu_item, FileNamePair *pair) (cancel_group_change_callback, pair->file, _("Cancel Group Change?"), - _("Changing group"), + _("Changing group."), NULL); /* FIXME bugzilla.gnome.org 42397: Parent this? */ nautilus_file_set_group (pair->file, pair->name, @@ -1537,7 +1538,7 @@ activate_owner_callback (GtkMenuItem *menu_item, FileNamePair *pair) (cancel_owner_change_callback, pair->file, _("Cancel Owner Change?"), - _("Changing owner"), + _("Changing owner."), NULL); /* FIXME bugzilla.gnome.org 42397: Parent this? */ nautilus_file_set_owner (pair->file, pair->name, @@ -2667,10 +2668,12 @@ static void add_permissions_checkbox (FMPropertiesWindow *window, GtkTable *table, int row, int column, - GnomeVFSFilePermissions permission_to_check) + GnomeVFSFilePermissions permission_to_check, + GtkLabel *label_for) { GtkWidget *check_button; gchar *label; + gboolean a11y_enabled; if (column == PERMISSIONS_CHECKBOXES_READ_COLUMN) { label = _("_Read"); @@ -2691,6 +2694,12 @@ add_permissions_checkbox (FMPropertiesWindow *window, set_up_permissions_checkbox (window, check_button, permission_to_check); + + a11y_enabled = GTK_IS_ACCESSIBLE (gtk_widget_get_accessible (check_button)); + if (a11y_enabled) { + eel_accessibility_set_up_label_widget_relation (GTK_WIDGET (label_for), + check_button); + } } static GtkWidget * @@ -2845,6 +2854,9 @@ create_permissions_page (FMPropertiesWindow *window) guint checkbox_titles_row; GtkLabel *group_label; GtkLabel *owner_label; + GtkLabel *owner_perm_label; + GtkLabel *group_perm_label; + GtkLabel *other_perm_label; GtkOptionMenu *group_menu; GtkOptionMenu *owner_menu; GList *file_list; @@ -2877,7 +2889,7 @@ create_permissions_page (FMPropertiesWindow *window) owner_label = attach_title_field (page_table, last_row, _("File _owner:")); /* Option menu in this case. */ owner_menu = attach_owner_menu (page_table, last_row, get_target_file (window)); - gtk_label_set_mnemonic_widget (GTK_LABEL (owner_label), + gtk_label_set_mnemonic_widget (owner_label, GTK_WIDGET (owner_menu)); } else { attach_title_field (page_table, last_row, _("File owner:")); @@ -2896,7 +2908,7 @@ create_permissions_page (FMPropertiesWindow *window) /* Option menu in this case. */ group_menu = attach_group_menu (page_table, last_row, get_target_file (window)); - gtk_label_set_mnemonic_widget (GTK_LABEL (group_label), + gtk_label_set_mnemonic_widget (group_label, GTK_WIDGET (group_menu)); } else { last_row = append_title_field (page_table, @@ -2912,9 +2924,9 @@ create_permissions_page (FMPropertiesWindow *window) append_separator (page_table); - checkbox_titles_row = append_title_field (page_table, _("Owner:"), NULL); - append_title_field (page_table, _("Group:"), NULL); - append_title_field (page_table, _("Others:"), NULL); + checkbox_titles_row = append_title_field (page_table, _("Owner:"), &owner_perm_label); + append_title_field (page_table, _("Group:"), &group_perm_label); + append_title_field (page_table, _("Others:"), &other_perm_label); check_button_table = GTK_TABLE (gtk_table_new (PERMISSIONS_CHECKBOXES_ROW_COUNT, @@ -2932,55 +2944,64 @@ create_permissions_page (FMPropertiesWindow *window) check_button_table, PERMISSIONS_CHECKBOXES_OWNER_ROW, PERMISSIONS_CHECKBOXES_READ_COLUMN, - GNOME_VFS_PERM_USER_READ); + GNOME_VFS_PERM_USER_READ, + owner_perm_label); add_permissions_checkbox (window, check_button_table, PERMISSIONS_CHECKBOXES_OWNER_ROW, PERMISSIONS_CHECKBOXES_WRITE_COLUMN, - GNOME_VFS_PERM_USER_WRITE); + GNOME_VFS_PERM_USER_WRITE, + owner_perm_label); add_permissions_checkbox (window, check_button_table, PERMISSIONS_CHECKBOXES_OWNER_ROW, PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN, - GNOME_VFS_PERM_USER_EXEC); + GNOME_VFS_PERM_USER_EXEC, + owner_perm_label); add_permissions_checkbox (window, check_button_table, PERMISSIONS_CHECKBOXES_GROUP_ROW, PERMISSIONS_CHECKBOXES_READ_COLUMN, - GNOME_VFS_PERM_GROUP_READ); + GNOME_VFS_PERM_GROUP_READ, + group_perm_label); add_permissions_checkbox (window, check_button_table, PERMISSIONS_CHECKBOXES_GROUP_ROW, PERMISSIONS_CHECKBOXES_WRITE_COLUMN, - GNOME_VFS_PERM_GROUP_WRITE); + GNOME_VFS_PERM_GROUP_WRITE, + group_perm_label); add_permissions_checkbox (window, check_button_table, PERMISSIONS_CHECKBOXES_GROUP_ROW, PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN, - GNOME_VFS_PERM_GROUP_EXEC); + GNOME_VFS_PERM_GROUP_EXEC, + group_perm_label); add_permissions_checkbox (window, check_button_table, PERMISSIONS_CHECKBOXES_OTHERS_ROW, PERMISSIONS_CHECKBOXES_READ_COLUMN, - GNOME_VFS_PERM_OTHER_READ); + GNOME_VFS_PERM_OTHER_READ, + other_perm_label); add_permissions_checkbox (window, check_button_table, PERMISSIONS_CHECKBOXES_OTHERS_ROW, PERMISSIONS_CHECKBOXES_WRITE_COLUMN, - GNOME_VFS_PERM_OTHER_WRITE); + GNOME_VFS_PERM_OTHER_WRITE, + other_perm_label); add_permissions_checkbox (window, check_button_table, PERMISSIONS_CHECKBOXES_OTHERS_ROW, PERMISSIONS_CHECKBOXES_EXECUTE_COLUMN, - GNOME_VFS_PERM_OTHER_EXEC); + GNOME_VFS_PERM_OTHER_EXEC, + other_perm_label); append_separator (page_table); @@ -3115,7 +3136,7 @@ static StartupData * startup_data_new (GList *original_files, GList *target_files, const char *pending_key, - FMDirectoryView *directory_view) + GtkWidget *parent_widget) { StartupData *data; GList *l; @@ -3123,7 +3144,7 @@ startup_data_new (GList *original_files, data = g_new0 (StartupData, 1); data->original_files = nautilus_file_list_copy (original_files); data->target_files = nautilus_file_list_copy (target_files); - data->directory_view = directory_view; + data->parent_widget = parent_widget; data->pending_key = g_strdup (pending_key); data->pending_files = g_hash_table_new (g_direct_hash, g_direct_equal); @@ -3149,19 +3170,15 @@ static void help_button_callback (GtkWidget *widget, GtkWidget *property_window) { GError *error = NULL; - char *message; egg_help_display_desktop_on_screen (NULL, "user-guide", "wgosnautilus.xml", "gosnautilus-51", gtk_window_get_screen (GTK_WINDOW (property_window)), &error); if (error) { - message = g_strdup_printf (_("There was an error displaying help: \n%s"), - error->message); - eel_show_error_dialog (message, _("Couldn't show help"), + eel_show_error_dialog (_("There was an error displaying help."), error->message, _("Couldn't Show Help"), GTK_WINDOW (property_window)); g_error_free (error); - g_free (message); } } @@ -3183,7 +3200,7 @@ create_properties_window (StartupData *startup_data) gtk_window_set_wmclass (GTK_WINDOW (window), "file_properties", "Nautilus"); gtk_window_set_resizable (GTK_WINDOW (window), FALSE); gtk_window_set_screen (GTK_WINDOW (window), - gtk_widget_get_screen (GTK_WIDGET (startup_data->directory_view))); + gtk_widget_get_screen (startup_data->parent_widget)); /* Set initial window title */ update_properties_window_title (window); @@ -3354,9 +3371,9 @@ cancel_create_properties_window_callback (gpointer callback_data) } static void -directory_view_destroyed_callback (FMDirectoryView *view, gpointer callback_data) +parent_widget_destroyed_callback (GtkWidget *widget, gpointer callback_data) { - g_assert (view == ((StartupData *)callback_data)->directory_view); + g_assert (widget == ((StartupData *)callback_data)->parent_widget); remove_pending ((StartupData *)callback_data, TRUE, TRUE, FALSE); } @@ -3389,8 +3406,8 @@ remove_pending (StartupData *startup_data, (cancel_create_properties_window_callback, startup_data); } if (cancel_destroy_handler) { - g_signal_handlers_disconnect_by_func (startup_data->directory_view, - G_CALLBACK (directory_view_destroyed_callback), + g_signal_handlers_disconnect_by_func (startup_data->parent_widget, + G_CALLBACK (parent_widget_destroyed_callback), startup_data); } @@ -3432,7 +3449,7 @@ is_directory_ready_callback (NautilusFile *file, void fm_properties_window_present (GList *original_files, - FMDirectoryView *directory_view) + GtkWidget *parent_widget) { GList *l, *next; GtkWidget *parent_window; @@ -3442,7 +3459,7 @@ fm_properties_window_present (GList *original_files, char *pending_key; g_return_if_fail (original_files != NULL); - g_return_if_fail (FM_IS_DIRECTORY_VIEW (directory_view)); + g_return_if_fail (GTK_IS_WIDGET (parent_widget)); /* Create the hash tables first time through. */ if (windows == NULL) { @@ -3459,7 +3476,7 @@ fm_properties_window_present (GList *original_files, existing_window = get_existing_window (original_files); if (existing_window != NULL) { gtk_window_set_screen (existing_window, - gtk_widget_get_screen (GTK_WIDGET (directory_view))); + gtk_widget_get_screen (parent_widget)); gtk_window_present (existing_window); return; } @@ -3477,7 +3494,7 @@ fm_properties_window_present (GList *original_files, startup_data = startup_data_new (original_files, target_files, pending_key, - directory_view); + parent_widget); nautilus_file_list_free (target_files); g_free(pending_key); @@ -3487,16 +3504,16 @@ fm_properties_window_present (GList *original_files, */ g_hash_table_insert (pending_lists, startup_data->pending_key, startup_data->pending_key); - g_signal_connect (directory_view, "destroy", - G_CALLBACK (directory_view_destroyed_callback), startup_data); + g_signal_connect (parent_widget, "destroy", + G_CALLBACK (parent_widget_destroyed_callback), startup_data); - parent_window = gtk_widget_get_ancestor (GTK_WIDGET (directory_view), GTK_TYPE_WINDOW); + parent_window = gtk_widget_get_ancestor (parent_widget, GTK_TYPE_WINDOW); eel_timed_wait_start (cancel_create_properties_window_callback, startup_data, _("Cancel Showing Properties Window?"), - _("Creating Properties window"), + _("Creating Properties window."), parent_window == NULL ? NULL : GTK_WINDOW (parent_window)); diff --git a/src/file-manager/fm-properties-window.h b/src/file-manager/fm-properties-window.h index 32d59d99a..b05c3b729 100644 --- a/src/file-manager/fm-properties-window.h +++ b/src/file-manager/fm-properties-window.h @@ -26,10 +26,9 @@ #ifndef FM_PROPERTIES_WINDOW_H #define FM_PROPERTIES_WINDOW_H -#include "fm-directory-view.h" - #include <gtk/gtkwindow.h> #include <libnautilus-private/nautilus-file.h> +#include <libnautilus/nautilus-view.h> typedef struct FMPropertiesWindow FMPropertiesWindow; @@ -60,6 +59,6 @@ typedef struct FMPropertiesWindowClass FMPropertiesWindowClass; GType fm_properties_window_get_type (void); void fm_properties_window_present (GList *files, - FMDirectoryView *directory_view); + GtkWidget *parent_widget); #endif /* FM_PROPERTIES_WINDOW_H */ diff --git a/src/file-manager/fm-search-list-view.c b/src/file-manager/fm-search-list-view.c index 682daa757..8b43400fc 100644 --- a/src/file-manager/fm-search-list-view.c +++ b/src/file-manager/fm-search-list-view.c @@ -163,8 +163,9 @@ load_location_callback (NautilusView *nautilus_view, char *location) } } #else - eel_show_error_dialog (_("Sorry, but the Medusa search service is not available because it is not installed."), - _("Search Service Not Available"), + eel_show_error_dialog (_("Sorry, but the Medusa search service is not available."), + _("Medusa is not installed."), + _("Search Service Not Available"), NULL); #endif @@ -179,7 +180,6 @@ real_load_error (FMDirectoryView *nautilus_view, GnomeVFSResult result) { GtkDialog *load_error_dialog; - char *error_string; /* Do not call parent's function; we handle all search errors * here and don't want fm-directory-view's default handling. @@ -190,42 +190,38 @@ real_load_error (FMDirectoryView *nautilus_view, load_error_dialog = eel_show_info_dialog (_("The search you have selected " "is newer than the index on your " "system. The search will return " - "no results right now. You can create a new " - " index by running \"medusa-indexd\" as root " + "no results right now."), + _("You can create a new " + "index by running \"medusa-indexd\" as root " "on the command line."), - _("Search for items that are too new"), + _("Search For Items That Are Too New"), NULL); break; case GNOME_VFS_ERROR_TOO_BIG: eel_show_error_dialog (_("Every indexed file on your computer " - "matches the criteria you selected. " - "You can check the spelling on your selections " + "matches the criteria you selected. "), + _("You can check the spelling on your selections " "or add more criteria to narrow your results."), - _("Error during search"), + _("Error During Search"), NULL); break; case GNOME_VFS_ERROR_SERVICE_NOT_AVAILABLE: /* FIXME: This dialog does not get shown because a slow search will be performed and will not return an error. */ eel_show_error_dialog (_("Find cannot open your file system index. " - "Your index may be missing or corrupt. You can " - "create a new index by running \"medusa-indexd\" as root " + "Your index may be missing or corrupt."), + _("You can create a new index by running \"medusa-indexd\" as root " "on the command line."), - _("Error reading file index"), + _("Error Reading File Index"), NULL); break; case GNOME_VFS_ERROR_CANCELLED: break; default: - error_string = g_strdup_printf (_("An error occurred while loading " - "this search's contents: " - "%s"), - gnome_vfs_result_to_string (result)); - eel_show_error_dialog (error_string, - _("Error during search"), - NULL); - g_free (error_string); - + eel_show_error_dialog (_("An error occurred while loading this search's contents."), + gnome_vfs_result_to_string (result), + _("Error During Search"), + NULL); } } @@ -234,7 +230,7 @@ real_load_error (FMDirectoryView *nautilus_view, static void display_indexed_search_problems_dialog (gboolean backup_search_is_available) { - const char *error_string, *title_string; + const char *error_string, *detail_string, *title_string; if (medusa_indexed_search_system_index_files_look_available ()) { /* There is an index on the system, but there is no @@ -242,17 +238,19 @@ display_indexed_search_problems_dialog (gboolean backup_search_is_available) confused. Tell the user this. */ error_string = backup_search_is_available ? N_("To do a fast search, Find requires an index " - "of the files on your system. " - "Find can't access your index right now " + "of the files on your system.") + : N_("To do a content search, Find requires an index " + "of the files on your system."); + detail_string = backup_search_is_available + ? N_("Find can't access your index right now " "so a slower search will be performed that " "doesn't use the index.") - : N_("To do a content search, Find requires an index " - "of the files on your system. " - "Find can't access your index right now. "); + : N_("Find can't access your index right now."); title_string = backup_search_is_available - ? N_("Fast searches are not available") - : N_("Content searches are not available"); + ? N_("Fast Searches Are Not Available") + : N_("Content Searches Are Not Available"); eel_show_error_dialog_with_details (error_string, + detail_string, title_string, _("Your index files are available " "but the Medusa search daemon, which handles " @@ -270,19 +268,23 @@ display_indexed_search_problems_dialog (gboolean backup_search_is_available) error_string = backup_search_is_available ? N_("To do a fast search, Find requires an index " "of the files on your system. " - "Your computer is currently creating that " - "index. Because Find cannot use an index, " - "this search may take several " - "minutes.") + "Your computer is currently creating that " + "index.") : N_("To do a content search, Find requires an index " "of the content on your system. " "Your computer is currently creating that " - "index. Content searches will be available " + "index."); + detail_string = backup_search_is_available + ? N_("Because Find cannot use an index, " + "this search may take several " + "minutes.") + : N_("Content searches will be available " "when the index is complete."); title_string = backup_search_is_available - ? N_("Indexed searches are not available") - : N_("Content searches are not available"); + ? N_("Indexed Searches Are Not Available") + : N_("Content Searches Are Not Available"); eel_show_error_dialog (error_string, + detail_string, title_string, NULL); } @@ -290,22 +292,26 @@ display_indexed_search_problems_dialog (gboolean backup_search_is_available) error_string = backup_search_is_available ? N_("To do a fast search, Find requires an index " "of the files on your system. No index " - "is available right now. You can create an " + "is available right now.") + : N_("To do a content search, Find requires an index " + "of the content on your system. No index is " + "available right now."); + detail_string = backup_search_is_available + ? N_("You can create an " "index by running \"medusa-indexd\" as root " "on the command line. Until a complete index " "is available, searches will " "take several minutes.") - : N_("To do a content search, Find requires an index " - "of the content on your system. No index is " - "available right now. You can create an " + : N_("You can create an " "index by running \"medusa-indexd\" as root " "on the command line. Until a complete index " "is available, content searches cannot be " "performed."); title_string = backup_search_is_available - ? N_("Indexed searches are not available") - : N_("Content searches are not available"); + ? N_("Indexed Searches Are Not Available") + : N_("Content Searches Are Not Available"); eel_show_error_dialog (error_string, + detail_string, title_string, NULL); } @@ -320,11 +326,12 @@ display_system_services_are_disabled_dialog (gboolean unindexed_search_is_availa char *details_string; details_string = nautilus_medusa_get_explanation_of_enabling (); - dialog_shown = eel_show_info_dialog_with_details (_("To do a fast search, Find requires an index of " + dialog_shown = eel_show_info_dialog_with_details (_("Fast searches are not enabled on your computer."), + _("To do a fast search, Find requires an index of " "the files on your system. Your system administrator " "has disabled fast search on your computer, so no index " "is available."), - _("Fast searches are not enabled on your computer"), + _("Fast Searches Not Enabled"), details_string, NULL); g_free (details_string); @@ -681,8 +688,8 @@ real_file_limit_reached (FMDirectoryView *view) * to the way files are collected in batches. So you can't assume that * no more than the constant limit are displayed. */ - eel_show_warning_dialog (_("Nautilus found more search results than it can display. " - "Some matching items will not be displayed. "), + eel_show_warning_dialog (_("Nautilus found more search results than it can display."), + _("Some matching items will not be displayed. "), _("Too Many Matches"), fm_directory_view_get_containing_window (view)); } diff --git a/src/file-manager/fm-tree-model.c b/src/file-manager/fm-tree-model.c new file mode 100644 index 000000000..bcee8867a --- /dev/null +++ b/src/file-manager/fm-tree-model.c @@ -0,0 +1,1797 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Copyright C) 2000, 2001 Eazel, Inc + * Copyright (C) 2002 Anders Carlsson + * Copyright (C) 2002 Bent Spoon Software + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Anders Carlsson <andersca@gnu.org> + * Darin Adler <darin@bentspoon.com> + */ + +/* fm-tree-model.c - model for the tree view */ + +#include <config.h> +#include "fm-tree-model.h" + +#include <eel/eel-glib-extensions.h> +#include <libgnome/gnome-i18n.h> +#include <libnautilus-private/nautilus-directory.h> +#include <libnautilus-private/nautilus-file-attributes.h> +#include <libnautilus-private/nautilus-file.h> +#include <libnautilus-private/nautilus-icon-factory.h> +#include <string.h> + +enum { + ROW_LOADED, + LAST_SIGNAL +}; + +static guint tree_model_signals[LAST_SIGNAL] = { 0 }; + +typedef gboolean (* FilePredicate) (NautilusFile *); + +/* The user_data of the GtkTreeIter is the TreeNode pointer. + * It's NULL for the dummy node. If it's NULL, then user_data2 + * is the TreeNode pointer to the parent. + */ + +typedef struct TreeNode TreeNode; +typedef struct FMTreeModelRoot FMTreeModelRoot; + +struct TreeNode { + /* part of this node for the file itself */ + int ref_count; + + NautilusFile *file; + char *display_name; + char *icon_name; + GdkPixbuf *closed_pixbuf; + GdkPixbuf *open_pixbuf; + + FMTreeModelRoot *root; + + TreeNode *parent; + TreeNode *next; + TreeNode *prev; + + /* part of the node used only for directories */ + int dummy_child_ref_count; + int all_children_ref_count; + + NautilusDirectory *directory; + guint done_loading_id; + guint files_added_id; + guint files_changed_id; + + TreeNode *first_child; + + /* misc. flags */ + guint done_loading : 1; + guint inserting_first_child : 1; + guint inserted : 1; +}; + +struct FMTreeModelDetails { + int stamp; + + TreeNode *root_node; + + guint monitoring_update_idle_id; + + gboolean show_hidden_files; + gboolean show_backup_files; + gboolean show_only_directories; +}; + +struct FMTreeModelRoot { + FMTreeModel *model; + + /* separate hash table for each root node needed */ + GHashTable *file_to_node_map; + + TreeNode *root_node; + + gulong changed_handler_id; +}; + +typedef struct { + NautilusDirectory *directory; + FMTreeModel *model; +} DoneLoadingParameters; + +static GObjectClass *parent_class; + +static void schedule_monitoring_update (FMTreeModel *model); +static void destroy_node_without_reporting (FMTreeModel *model, + TreeNode *node); +static void report_node_contents_changed (FMTreeModel *model, + TreeNode *node); + +static guint +fm_tree_model_get_flags (GtkTreeModel *tree_model) +{ + return GTK_TREE_MODEL_ITERS_PERSIST; +} + +static void +object_unref_if_not_NULL (gpointer object) +{ + if (object == NULL) { + return; + } + g_object_unref (object); +} + +static FMTreeModelRoot * +tree_model_root_new (FMTreeModel *model) +{ + FMTreeModelRoot *root; + + root = g_new0 (FMTreeModelRoot, 1); + root->model = model; + root->file_to_node_map = g_hash_table_new (NULL, NULL); + + return root; +} + +static TreeNode * +tree_node_new (NautilusFile *file, FMTreeModelRoot *root) +{ + TreeNode *node; + + node = g_new0 (TreeNode, 1); + node->file = nautilus_file_ref (file); + node->root = root; + return node; +} + +static void +tree_node_unparent (FMTreeModel *model, TreeNode *node) +{ + TreeNode *parent, *next, *prev; + + parent = node->parent; + next = node->next; + prev = node->prev; + + if (parent == NULL && + node == model->details->root_node) { + /* it's the first root node -> if there is a next then let it be the first root node */ + model->details->root_node = next; + } + + if (next != NULL) { + next->prev = prev; + } + if (prev == NULL && parent != NULL) { + g_assert (parent->first_child == node); + parent->first_child = next; + } else if (prev != NULL) { + prev->next = next; + } + + node->parent = NULL; + node->next = NULL; + node->prev = NULL; + node->root = NULL; +} + +static void +tree_node_destroy (FMTreeModel *model, TreeNode *node) +{ + g_assert (node->first_child == NULL); + g_assert (node->ref_count == 0); + + tree_node_unparent (model, node); + + g_object_unref (node->file); + g_free (node->display_name); + g_free (node->icon_name); + object_unref_if_not_NULL (node->closed_pixbuf); + object_unref_if_not_NULL (node->open_pixbuf); + + g_assert (node->done_loading_id == 0); + g_assert (node->files_added_id == 0); + g_assert (node->files_changed_id == 0); + object_unref_if_not_NULL (node->directory); + + g_free (node); +} + +static void +tree_node_parent (TreeNode *node, TreeNode *parent) +{ + TreeNode *first_child; + + g_assert (parent != NULL); + g_assert (node->parent == NULL); + g_assert (node->prev == NULL); + g_assert (node->next == NULL); + + first_child = parent->first_child; + + node->parent = parent; + node->root = parent->root; + node->next = first_child; + + if (first_child != NULL) { + g_assert (first_child->prev == NULL); + first_child->prev = node; + } + + parent->first_child = node; +} + +static GdkPixbuf * +tree_node_get_pixbuf_from_factory (TreeNode *node, + const char *modifier) +{ + if (node->parent == NULL) { + return nautilus_icon_factory_get_pixbuf_from_name + (node->icon_name, NULL, + NAUTILUS_ICON_SIZE_FOR_MENUS, NULL); + } + return nautilus_icon_factory_get_pixbuf_for_file + (node->file, modifier, NAUTILUS_ICON_SIZE_FOR_MENUS); +} + +static gboolean +tree_node_update_pixbuf (TreeNode *node, + GdkPixbuf **pixbuf_storage, + const char *modifier) +{ + GdkPixbuf *pixbuf; + + if (*pixbuf_storage == NULL) { + return FALSE; + } + pixbuf = tree_node_get_pixbuf_from_factory (node, modifier); + if (pixbuf == *pixbuf_storage) { + g_object_unref (pixbuf); + return FALSE; + } + g_object_unref (*pixbuf_storage); + *pixbuf_storage = pixbuf; + return TRUE; +} + +static gboolean +tree_node_update_closed_pixbuf (TreeNode *node) +{ + return tree_node_update_pixbuf (node, &node->closed_pixbuf, NULL); +} + +static gboolean +tree_node_update_open_pixbuf (TreeNode *node) +{ + return tree_node_update_pixbuf (node, &node->open_pixbuf, "accept"); +} + +static gboolean +tree_node_update_display_name (TreeNode *node) +{ + char *display_name; + + if (node->display_name == NULL) { + return FALSE; + } + /* don't update root node display names */ + if (node->parent == NULL) { + return FALSE; + } + display_name = nautilus_file_get_display_name (node->file); + if (strcmp (display_name, node->display_name) == 0) { + g_free (display_name); + return FALSE; + } + g_free (node->display_name); + node->display_name = NULL; + return TRUE; +} + +static GdkPixbuf * +tree_node_get_closed_pixbuf (TreeNode *node) +{ + if (node->closed_pixbuf == NULL) { + node->closed_pixbuf = tree_node_get_pixbuf_from_factory (node, NULL); + } + return node->closed_pixbuf; +} + +static GdkPixbuf * +tree_node_get_open_pixbuf (TreeNode *node) +{ + if (node->open_pixbuf == NULL) { + node->open_pixbuf = tree_node_get_pixbuf_from_factory (node, "accept"); + } + return node->open_pixbuf; +} + +static const char * +tree_node_get_display_name (TreeNode *node) +{ + if (node->display_name == NULL) { + node->display_name = nautilus_file_get_display_name (node->file); + } + return node->display_name; +} + +static gboolean +tree_node_has_dummy_child (TreeNode *node) +{ + return node->directory != NULL + && (!node->done_loading + || node->first_child == NULL + || node->inserting_first_child); +} + +static int +tree_node_get_child_index (TreeNode *parent, TreeNode *child) +{ + int i; + TreeNode *node; + + if (child == NULL) { + g_assert (tree_node_has_dummy_child (parent)); + return 0; + } + + i = tree_node_has_dummy_child (parent) ? 1 : 0; + for (node = parent->first_child; node != NULL; node = node->next, i++) { + if (child == node) { + return i; + } + } + + g_assert_not_reached (); + return 0; +} + +static gboolean +make_iter_invalid (GtkTreeIter *iter) +{ + iter->stamp = 0; + iter->user_data = NULL; + iter->user_data2 = NULL; + iter->user_data3 = NULL; + return FALSE; +} + +static gboolean +make_iter_for_node (TreeNode *node, GtkTreeIter *iter, int stamp) +{ + if (node == NULL) { + return make_iter_invalid (iter); + } + iter->stamp = stamp; + iter->user_data = node; + iter->user_data2 = NULL; + iter->user_data3 = NULL; + return TRUE; +} + +static gboolean +make_iter_for_dummy_row (TreeNode *parent, GtkTreeIter *iter, int stamp) +{ + g_assert (tree_node_has_dummy_child (parent)); + g_assert (parent != NULL); + iter->stamp = stamp; + iter->user_data = NULL; + iter->user_data2 = parent; + iter->user_data3 = NULL; + return TRUE; +} + +static TreeNode * +get_node_from_file (FMTreeModelRoot *root, NautilusFile *file) +{ + return g_hash_table_lookup (root->file_to_node_map, file); +} + +static TreeNode * +get_parent_node_from_file (FMTreeModelRoot *root, NautilusFile *file) +{ + NautilusFile *parent_file; + TreeNode *parent_node; + + parent_file = nautilus_file_get_parent (file); + parent_node = get_node_from_file (root, parent_file); + nautilus_file_unref (parent_file); + return parent_node; +} + +static TreeNode * +create_node_for_file (FMTreeModelRoot *root, NautilusFile *file) +{ + TreeNode *node; + + g_assert (get_node_from_file (root, file) == NULL); + node = tree_node_new (file, root); + g_hash_table_insert (root->file_to_node_map, node->file, node); + return node; +} + +#if LOG_REF_COUNTS + +static char * +get_node_uri (GtkTreeIter *iter) +{ + TreeNode *node, *parent; + char *parent_uri, *node_uri; + + node = iter->user_data; + if (node != NULL) { + return nautilus_file_get_uri (node->file); + } + + parent = iter->user_data2; + parent_uri = nautilus_file_get_uri (parent->file); + node_uri = g_strconcat (parent_uri, " -- DUMMY", NULL); + g_free (parent_uri); + return node_uri; +} + +#endif + +static void +decrement_ref_count (FMTreeModel *model, TreeNode *node, int count) +{ + node->all_children_ref_count -= count; + if (node->all_children_ref_count == 0) { + schedule_monitoring_update (model); + } +} + +static void +abandon_node_ref_count (FMTreeModel *model, TreeNode *node) +{ + if (node->parent != NULL) { + decrement_ref_count (model, node->parent, node->ref_count); +#if LOG_REF_COUNTS + if (node->ref_count != 0) { + char *uri; + + uri = nautilus_file_get_uri (node->file); + g_message ("abandoning %d ref of %s, count is now %d", + node->ref_count, uri, node->parent->all_children_ref_count); + g_free (uri); + } +#endif + } + node->ref_count = 0; +} + +static void +abandon_dummy_row_ref_count (FMTreeModel *model, TreeNode *node) +{ + decrement_ref_count (model, node, node->dummy_child_ref_count); + if (node->dummy_child_ref_count != 0) { +#if LOG_REF_COUNTS + char *uri; + + uri = nautilus_file_get_uri (node->file); + g_message ("abandoning %d ref of %s -- DUMMY, count is now %d", + node->dummy_child_ref_count, uri, node->all_children_ref_count); + g_free (uri); +#endif + } + node->dummy_child_ref_count = 0; +} + +static void +report_row_inserted (FMTreeModel *model, GtkTreeIter *iter) +{ + GtkTreePath *path; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); + gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), path, iter); + gtk_tree_path_free (path); +} + +static void +report_row_contents_changed (FMTreeModel *model, GtkTreeIter *iter) +{ + GtkTreePath *path; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); + gtk_tree_model_row_changed (GTK_TREE_MODEL (model), path, iter); + gtk_tree_path_free (path); +} + +static void +report_row_has_child_toggled (FMTreeModel *model, GtkTreeIter *iter) +{ + GtkTreePath *path; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), iter); + gtk_tree_model_row_has_child_toggled (GTK_TREE_MODEL (model), path, iter); + gtk_tree_path_free (path); +} + +static GtkTreePath * +get_node_path (FMTreeModel *model, TreeNode *node) +{ + GtkTreeIter iter; + + make_iter_for_node (node, &iter, model->details->stamp); + return gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); +} + +static void +report_dummy_row_inserted (FMTreeModel *model, TreeNode *parent) +{ + GtkTreeIter iter; + + if (!parent->inserted) { + return; + } + make_iter_for_dummy_row (parent, &iter, model->details->stamp); + report_row_inserted (model, &iter); +} + +static void +report_dummy_row_deleted (FMTreeModel *model, TreeNode *parent) +{ + GtkTreeIter iter; + GtkTreePath *path; + + abandon_dummy_row_ref_count (model, parent); + if (!parent->inserted) { + return; + } + make_iter_for_node (parent, &iter, model->details->stamp); + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + gtk_tree_path_append_index (path, 0); + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + gtk_tree_path_free (path); +} + +static void +report_node_inserted (FMTreeModel *model, TreeNode *node) +{ + GtkTreeIter iter; + + make_iter_for_node (node, &iter, model->details->stamp); + report_row_inserted (model, &iter); + node->inserted = TRUE; + + if (node->directory != NULL) { + report_row_has_child_toggled (model, &iter); + } + if (tree_node_has_dummy_child (node)) { + report_dummy_row_inserted (model, node); + } +} + +static void +report_node_contents_changed (FMTreeModel *model, TreeNode *node) +{ + GtkTreeIter iter; + + if (!node->inserted) { + return; + } + make_iter_for_node (node, &iter, model->details->stamp); + report_row_contents_changed (model, &iter); +} + +static void +report_node_has_child_toggled (FMTreeModel *model, TreeNode *node) +{ + GtkTreeIter iter; + + if (!node->inserted) { + return; + } + make_iter_for_node (node, &iter, model->details->stamp); + report_row_has_child_toggled (model, &iter); +} + +static void +report_dummy_row_contents_changed (FMTreeModel *model, TreeNode *parent) +{ + GtkTreeIter iter; + + if (!parent->inserted) { + return; + } + make_iter_for_dummy_row (parent, &iter, model->details->stamp); + report_row_contents_changed (model, &iter); +} + +static void +stop_monitoring_directory (FMTreeModel *model, TreeNode *node) +{ + NautilusDirectory *directory; + + if (node->done_loading_id == 0) { + g_assert (node->files_added_id == 0); + g_assert (node->files_changed_id == 0); + return; + } + + directory = node->directory; + + g_signal_handler_disconnect (node->directory, node->done_loading_id); + g_signal_handler_disconnect (node->directory, node->files_added_id); + g_signal_handler_disconnect (node->directory, node->files_changed_id); + + node->done_loading_id = 0; + node->files_added_id = 0; + node->files_changed_id = 0; + + nautilus_directory_file_monitor_remove (node->directory, model); +} + +static void +destroy_children_without_reporting (FMTreeModel *model, TreeNode *parent) +{ + while (parent->first_child != NULL) { + destroy_node_without_reporting (model, parent->first_child); + } +} + +static void +destroy_node_without_reporting (FMTreeModel *model, TreeNode *node) +{ + abandon_node_ref_count (model, node); + stop_monitoring_directory (model, node); + node->inserted = FALSE; + destroy_children_without_reporting (model, node); + g_hash_table_remove (node->root->file_to_node_map, node->file); + tree_node_destroy (model, node); +} + +static void +destroy_node (FMTreeModel *model, TreeNode *node) +{ + TreeNode *parent; + gboolean parent_had_dummy_child; + GtkTreePath *path; + + parent = node->parent; + parent_had_dummy_child = tree_node_has_dummy_child (parent); + + path = get_node_path (model, node); + + destroy_node_without_reporting (model, node); + + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + gtk_tree_path_free (path); + + if (tree_node_has_dummy_child (parent)) { + if (!parent_had_dummy_child) { + report_dummy_row_inserted (model, parent); + } + } else { + g_assert (!parent_had_dummy_child); + } +} + +static void +destroy_children (FMTreeModel *model, TreeNode *parent) +{ + while (parent->first_child != NULL) { + destroy_node (model, parent->first_child); + } +} + +static void +destroy_children_by_function (FMTreeModel *model, TreeNode *parent, FilePredicate f) +{ + TreeNode *child, *next; + + for (child = parent->first_child; child != NULL; child = next) { + next = child->next; + if (f (child->file)) { + destroy_node (model, child); + } else { + destroy_children_by_function (model, child, f); + } + } +} + +static void +destroy_by_function (FMTreeModel *model, FilePredicate f) +{ + TreeNode *node; + for (node = model->details->root_node; node != NULL; node = node->next) { + destroy_children_by_function (model, node, f); + } +} + +static gboolean +update_node_without_reporting (FMTreeModel *model, TreeNode *node) +{ + gboolean changed; + + changed = FALSE; + + if (node->directory == NULL && nautilus_file_is_directory (node->file)) { + node->directory = nautilus_directory_get_for_file (node->file); + } else if (node->directory != NULL && !nautilus_file_is_directory (node->file)) { + stop_monitoring_directory (model, node); + destroy_children (model, node); + nautilus_directory_unref (node->directory); + node->directory = NULL; + } + + changed |= tree_node_update_display_name (node); + changed |= tree_node_update_closed_pixbuf (node); + changed |= tree_node_update_open_pixbuf (node); + + return changed; +} + +static void +insert_node (FMTreeModel *model, TreeNode *parent, TreeNode *node) +{ + gboolean parent_empty; + + parent_empty = parent->first_child == NULL; + if (parent_empty) { + parent->inserting_first_child = TRUE; + } + + tree_node_parent (node, parent); + + update_node_without_reporting (model, node); + report_node_inserted (model, node); + + if (parent_empty) { + parent->inserting_first_child = FALSE; + if (!tree_node_has_dummy_child (parent)) { + report_dummy_row_deleted (model, parent); + } + } +} + +static void +reparent_node (FMTreeModel *model, TreeNode *node) +{ + GtkTreePath *path; + TreeNode *new_parent; + + new_parent = get_parent_node_from_file (node->root, node->file); + if (new_parent == NULL || new_parent->directory == NULL) { + destroy_node (model, node); + return; + } + + path = get_node_path (model, node); + + abandon_node_ref_count (model, node); + tree_node_unparent (model, node); + + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + gtk_tree_path_free (path); + + insert_node (model, new_parent, node); +} + +static gboolean +should_show_file (FMTreeModel *model, NautilusFile *file) +{ + gboolean should; + TreeNode *node; + + should = nautilus_file_should_show (file, + model->details->show_hidden_files, + model->details->show_backup_files); + + if (should + && model->details->show_only_directories + &&! nautilus_file_is_directory (file)) { + should = FALSE; + } + + if (should && nautilus_file_is_gone (file)) { + should = FALSE; + } + + for (node = model->details->root_node; node != NULL; node = node->next) { + if (!should && node != NULL && file == node->file) { + should = TRUE; + } + } + + return should; +} + +static void +update_node (FMTreeModel *model, TreeNode *node) +{ + gboolean had_dummy_child, has_dummy_child; + gboolean had_directory, has_directory; + gboolean changed; + + if (!should_show_file (model, node->file)) { + destroy_node (model, node); + return; + } + + if (node->parent != NULL && node->parent->directory != NULL + && !nautilus_directory_contains_file (node->parent->directory, node->file)) { + reparent_node (model, node); + return; + } + + had_dummy_child = tree_node_has_dummy_child (node); + had_directory = node->directory != NULL; + + changed = update_node_without_reporting (model, node); + + has_dummy_child = tree_node_has_dummy_child (node); + has_directory = node->directory != NULL; + + if (had_dummy_child != has_dummy_child) { + if (has_dummy_child) { + report_dummy_row_inserted (model, node); + } else { + report_dummy_row_deleted (model, node); + } + } + if (had_directory != has_directory) { + report_node_has_child_toggled (model, node); + } + + if (changed) { + report_node_contents_changed (model, node); + } +} + +static void +process_file_change (FMTreeModelRoot *root, + NautilusFile *file) +{ + TreeNode *node, *parent; + + node = get_node_from_file (root, file); + if (node != NULL) { + update_node (root->model, node); + return; + } + + if (!should_show_file (root->model, file)) { + return; + } + + parent = get_parent_node_from_file (root, file); + if (parent == NULL) { + return; + } + + insert_node (root->model, parent, create_node_for_file (root, file)); +} + +static void +files_changed_callback (NautilusDirectory *directory, + GList *changed_files, + gpointer callback_data) +{ + FMTreeModelRoot *root; + GList *node; + + root = (FMTreeModelRoot *) (callback_data); + + for (node = changed_files; node != NULL; node = node->next) { + process_file_change (root, NAUTILUS_FILE (node->data)); + } +} + +static void +set_done_loading (FMTreeModel *model, TreeNode *node, gboolean done_loading) +{ + gboolean had_dummy; + + if (node == NULL || node->done_loading == done_loading) { + return; + } + + had_dummy = tree_node_has_dummy_child (node); + + node->done_loading = done_loading; + + if (tree_node_has_dummy_child (node)) { + if (had_dummy) { + report_dummy_row_contents_changed (model, node); + } else { + report_dummy_row_inserted (model, node); + } + } else { + if (had_dummy) { + report_dummy_row_deleted (model, node); + } else { + g_assert_not_reached (); + } + } +} + +static void +done_loading_callback (NautilusDirectory *directory, + FMTreeModelRoot *root) +{ + NautilusFile *file; + TreeNode *node; + GtkTreeIter iter; + + file = nautilus_directory_get_corresponding_file (directory); + node = get_node_from_file (root, file); + g_assert (node != NULL); + set_done_loading (root->model, node, TRUE); + nautilus_file_unref (file); + + make_iter_for_node (node, &iter, root->model->details->stamp); + g_signal_emit_by_name (root->model, "row_loaded", &iter); +} + +static NautilusFileAttributes +get_tree_monitor_attributes (void) +{ + NautilusFileAttributes attributes; + + attributes = nautilus_icon_factory_get_required_file_attributes (); + attributes |= NAUTILUS_FILE_ATTRIBUTE_IS_DIRECTORY | + NAUTILUS_FILE_ATTRIBUTE_DISPLAY_NAME; + + return attributes; +} + +static void +start_monitoring_directory (FMTreeModel *model, TreeNode *node) +{ + NautilusDirectory *directory; + NautilusFileAttributes attributes; + + if (node->done_loading_id != 0) { + return; + } + + g_assert (node->files_added_id == 0); + g_assert (node->files_changed_id == 0); + + directory = node->directory; + + node->done_loading_id = g_signal_connect + (directory, "done_loading", + G_CALLBACK (done_loading_callback), node->root); + node->files_added_id = g_signal_connect + (directory, "files_added", + G_CALLBACK (files_changed_callback), node->root); + node->files_changed_id = g_signal_connect + (directory, "files_changed", + G_CALLBACK (files_changed_callback), node->root); + + set_done_loading (model, node, nautilus_directory_are_all_files_seen (directory)); + + attributes = get_tree_monitor_attributes (); + nautilus_directory_file_monitor_add (directory, model, + model->details->show_hidden_files, + model->details->show_backup_files, + attributes, files_changed_callback, node->root); +} + +static int +fm_tree_model_get_n_columns (GtkTreeModel *model) +{ + return FM_TREE_MODEL_NUM_COLUMNS; +} + +static GType +fm_tree_model_get_column_type (GtkTreeModel *model, int index) +{ + switch (index) { + case FM_TREE_MODEL_DISPLAY_NAME_COLUMN: + return G_TYPE_STRING; + case FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN: + return GDK_TYPE_PIXBUF; + case FM_TREE_MODEL_OPEN_PIXBUF_COLUMN: + return GDK_TYPE_PIXBUF; + case FM_TREE_MODEL_FONT_STYLE_COLUMN: + return PANGO_TYPE_STYLE; + case FM_TREE_MODEL_FONT_WEIGHT_COLUMN: + return PANGO_TYPE_WEIGHT; + default: + g_assert_not_reached (); + } + + return G_TYPE_INVALID; +} + +static gboolean +iter_is_valid (FMTreeModel *model, const GtkTreeIter *iter) +{ + TreeNode *node, *parent; + + if (iter->stamp != model->details->stamp) { + return FALSE; + } + + node = iter->user_data; + parent = iter->user_data2; + if (node == NULL) { + if (parent != NULL) { + if (!NAUTILUS_IS_FILE (parent->file)) { + return FALSE; + } + if (!tree_node_has_dummy_child (parent)) { + return FALSE; + } + } + } else { + if (!NAUTILUS_IS_FILE (node->file)) { + return FALSE; + } + if (parent != NULL) { + return FALSE; + } + } + if (iter->user_data3 != NULL) { + return FALSE; + } + return TRUE; +} + +static gboolean +fm_tree_model_get_iter (GtkTreeModel *model, GtkTreeIter *iter, GtkTreePath *path) +{ + int *indices; + GtkTreeIter parent; + int depth, i; + + indices = gtk_tree_path_get_indices (path); + depth = gtk_tree_path_get_depth (path); + + if (! gtk_tree_model_iter_nth_child (model, iter, NULL, indices[0])) { + return FALSE; + } + + for (i = 1; i < depth; i++) { + parent = *iter; + + if (! gtk_tree_model_iter_nth_child (model, iter, &parent, indices[i])) { + return FALSE; + } + } + + return TRUE; +} + +static GtkTreePath * +fm_tree_model_get_path (GtkTreeModel *model, GtkTreeIter *iter) +{ + FMTreeModel *tree_model; + TreeNode *node, *parent, *cnode; + GtkTreePath *path; + GtkTreeIter parent_iter; + int i; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), NULL); + tree_model = FM_TREE_MODEL (model); + g_return_val_if_fail (iter_is_valid (tree_model, iter), NULL); + + node = iter->user_data; + if (node == NULL) { + parent = iter->user_data2; + if (parent == NULL) { + return gtk_tree_path_new (); + } + } else { + parent = node->parent; + if (parent == NULL) { + i = 0; + for (cnode = tree_model->details->root_node; cnode != node; cnode = cnode->next) { + i++; + } + path = gtk_tree_path_new (); + gtk_tree_path_append_index (path, i); + return path; + } + } + + parent_iter.stamp = iter->stamp; + parent_iter.user_data = parent; + parent_iter.user_data2 = NULL; + parent_iter.user_data3 = NULL; + + path = fm_tree_model_get_path (model, &parent_iter); + + gtk_tree_path_append_index (path, tree_node_get_child_index (parent, node)); + + return path; +} + +static void +fm_tree_model_get_value (GtkTreeModel *model, GtkTreeIter *iter, int column, GValue *value) +{ + TreeNode *node, *parent; + + g_return_if_fail (FM_IS_TREE_MODEL (model)); + g_return_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter)); + + node = iter->user_data; + + switch (column) { + case FM_TREE_MODEL_DISPLAY_NAME_COLUMN: + g_value_init (value, G_TYPE_STRING); + if (node == NULL) { + parent = iter->user_data2; + g_value_set_static_string (value, parent->done_loading + ? _("(Empty)") : _("Loading...")); + } else { + g_value_set_string (value, tree_node_get_display_name (node)); + } + break; + case FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN: + g_value_init (value, GDK_TYPE_PIXBUF); + g_value_set_object (value, node == NULL ? NULL : tree_node_get_closed_pixbuf (node)); + break; + case FM_TREE_MODEL_OPEN_PIXBUF_COLUMN: + g_value_init (value, GDK_TYPE_PIXBUF); + g_value_set_object (value, node == NULL ? NULL : tree_node_get_open_pixbuf (node)); + break; + case FM_TREE_MODEL_FONT_STYLE_COLUMN: + g_value_init (value, PANGO_TYPE_STYLE); + if (node == NULL) { + g_value_set_enum (value, PANGO_STYLE_ITALIC); + } else { + g_value_set_enum (value, PANGO_STYLE_NORMAL); + } + break; + case FM_TREE_MODEL_FONT_WEIGHT_COLUMN: + g_value_init (value, PANGO_TYPE_STYLE); + if (node != NULL && node->parent == NULL) { + g_value_set_enum (value, PANGO_WEIGHT_BOLD); + } else { + g_value_set_enum (value, PANGO_WEIGHT_NORMAL); + } + break; + default: + g_assert_not_reached (); + } +} + +static gboolean +fm_tree_model_iter_next (GtkTreeModel *model, GtkTreeIter *iter) +{ + TreeNode *node, *parent, *next; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter), FALSE); + + node = iter->user_data; + + if (node == NULL) { + parent = iter->user_data2; + next = parent->first_child; + } else { + next = node->next; + } + + return make_iter_for_node (next, iter, iter->stamp); +} + +static gboolean +fm_tree_model_iter_children (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *parent_iter) +{ + TreeNode *parent; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), parent_iter), FALSE); + + parent = parent_iter->user_data; + if (parent == NULL) { + return make_iter_invalid (iter); + } + + if (tree_node_has_dummy_child (parent)) { + return make_iter_for_dummy_row (parent, iter, parent_iter->stamp); + } + return make_iter_for_node (parent->first_child, iter, parent_iter->stamp); +} + +static gboolean +fm_tree_model_iter_parent (GtkTreeModel *model, GtkTreeIter *iter, GtkTreeIter *child_iter) +{ TreeNode *child, *parent; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), child_iter), FALSE); + + child = child_iter->user_data; + + if (child == NULL) { + parent = child_iter->user_data2; + } else { + parent = child->parent; + } + + return make_iter_for_node (parent, iter, child_iter->stamp); +} + +static gboolean +fm_tree_model_iter_has_child (GtkTreeModel *model, GtkTreeIter *iter) +{ + gboolean has_child; + TreeNode *node; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter), FALSE); + + node = iter->user_data; + + has_child = node != NULL && node->directory != NULL; + +#if 0 + g_warning ("Node '%s' %s", + node && node->file ? nautilus_file_get_uri (node->file) : "no name", + has_child ? "has child" : "no child"); +#endif + + return has_child; +} + +static int +fm_tree_model_iter_n_children (GtkTreeModel *model, GtkTreeIter *iter) +{ + FMTreeModel *tree_model; + TreeNode *parent, *node; + int n; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (iter == NULL || iter_is_valid (FM_TREE_MODEL (model), iter), FALSE); + + tree_model = FM_TREE_MODEL (model); + + if (iter == NULL) { + return 1; + } + + parent = iter->user_data; + if (parent == NULL) { + return 0; + } + + n = tree_node_has_dummy_child (parent) ? 1 : 0; + for (node = parent->first_child; node != NULL; node = node->next) { + n++; + } + + return n; +} + +static gboolean +fm_tree_model_iter_nth_child (GtkTreeModel *model, GtkTreeIter *iter, + GtkTreeIter *parent_iter, int n) +{ + FMTreeModel *tree_model; + TreeNode *parent, *node; + int i; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), FALSE); + g_return_val_if_fail (parent_iter == NULL + || iter_is_valid (FM_TREE_MODEL (model), parent_iter), FALSE); + + tree_model = FM_TREE_MODEL (model); + + if (parent_iter == NULL) { + node = tree_model->details->root_node; + for (i = 0; i < n && node != NULL; i++, node = node->next); + return make_iter_for_node (node, iter, + tree_model->details->stamp); + } + + parent = parent_iter->user_data; + if (parent == NULL) { + return make_iter_invalid (iter); + } + + i = tree_node_has_dummy_child (parent) ? 1 : 0; + if (n == 0 && i == 1) { + return make_iter_for_dummy_row (parent, iter, parent_iter->stamp); + } + for (node = parent->first_child; i != n; i++, node = node->next) { + if (node == NULL) { + return make_iter_invalid (iter); + } + } + + return make_iter_for_node (node, iter, parent_iter->stamp); +} + +static void +update_monitoring (FMTreeModel *model, TreeNode *node) +{ + TreeNode *child; + + if (node->all_children_ref_count == 0) { + stop_monitoring_directory (model, node); + destroy_children (model, node); + } else { + for (child = node->first_child; child != NULL; child = child->next) { + update_monitoring (model, child); + } + start_monitoring_directory (model, node); + } +} + +static gboolean +update_monitoring_idle_callback (gpointer callback_data) +{ + FMTreeModel *model; + TreeNode *node; + + model = FM_TREE_MODEL (callback_data); + model->details->monitoring_update_idle_id = 0; + for (node = model->details->root_node; node != NULL; node = node->next) { + update_monitoring (model, node); + } + return FALSE; +} + +static void +schedule_monitoring_update (FMTreeModel *model) +{ + if (model->details->monitoring_update_idle_id == 0) { + model->details->monitoring_update_idle_id = + g_idle_add (update_monitoring_idle_callback, model); + } +} + +static void +stop_monitoring_directory_and_children (FMTreeModel *model, TreeNode *node) +{ + TreeNode *child; + + stop_monitoring_directory (model, node); + for (child = node->first_child; child != NULL; child = child->next) { + stop_monitoring_directory_and_children (model, child); + } +} + +static void +stop_monitoring (FMTreeModel *model) +{ + TreeNode *node; + + for (node = model->details->root_node; node != NULL; node = node->next) { + stop_monitoring_directory_and_children (model, node); + } +} + +static void +fm_tree_model_ref_node (GtkTreeModel *model, GtkTreeIter *iter) +{ + TreeNode *node, *parent; +#if LOG_REF_COUNTS + char *uri; +#endif + + g_return_if_fail (FM_IS_TREE_MODEL (model)); + g_return_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter)); + + node = iter->user_data; + if (node == NULL) { + parent = iter->user_data2; + g_assert (parent->dummy_child_ref_count >= 0); + ++parent->dummy_child_ref_count; + } else { + parent = node->parent; + g_assert (node->ref_count >= 0); + ++node->ref_count; + } + + if (parent != NULL) { + g_assert (parent->all_children_ref_count >= 0); + if (++parent->all_children_ref_count == 1) { + if (parent->first_child == NULL) { + parent->done_loading = FALSE; + } + schedule_monitoring_update (FM_TREE_MODEL (model)); + } +#if LOG_REF_COUNTS + uri = get_node_uri (iter); + g_message ("ref of %s, count is now %d", + uri, parent->all_children_ref_count); + g_free (uri); +#endif + } +} + +static void +fm_tree_model_unref_node (GtkTreeModel *model, GtkTreeIter *iter) +{ + TreeNode *node, *parent; +#if LOG_REF_COUNTS + char *uri; +#endif + + g_return_if_fail (FM_IS_TREE_MODEL (model)); + g_return_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter)); + + node = iter->user_data; + if (node == NULL) { + parent = iter->user_data2; + g_assert (parent->dummy_child_ref_count > 0); + --parent->dummy_child_ref_count; + } else { + parent = node->parent; + g_assert (node->ref_count > 0); + --node->ref_count; + } + + if (parent != NULL) { + g_assert (parent->all_children_ref_count > 0); +#if LOG_REF_COUNTS + uri = get_node_uri (iter); + g_message ("unref of %s, count is now %d", + uri, parent->all_children_ref_count - 1); + g_free (uri); +#endif + if (--parent->all_children_ref_count == 0) { + schedule_monitoring_update (FM_TREE_MODEL (model)); + } + } +} + +static void +root_node_file_changed_callback (NautilusFile *file, FMTreeModelRoot *root) +{ + if (root->root_node != NULL) { + update_node (root->model, root->root_node); + } +} + +void +fm_tree_model_add_root_uri (FMTreeModel *model, const char *root_uri, const char *display_name, const char *icon_name) +{ + NautilusFile *file; + TreeNode *node, *cnode; + NautilusFileAttributes attributes; + FMTreeModelRoot *newroot; + + file = nautilus_file_get (root_uri); + + newroot = tree_model_root_new (model); + node = create_node_for_file (newroot, file); + node->display_name = g_strdup (display_name); + node->icon_name = g_strdup (icon_name); + newroot->root_node = node; + node->parent = NULL; + if (model->details->root_node == NULL) { + model->details->root_node = node; + } else { + /* append it */ + for (cnode = model->details->root_node; cnode->next != NULL; cnode = cnode->next); + cnode->next = node; + node->prev = cnode; + } + + newroot->changed_handler_id = g_signal_connect (node->file, "changed", + G_CALLBACK (root_node_file_changed_callback), + node->root); + + attributes = get_tree_monitor_attributes (); + nautilus_file_monitor_add (file, model, attributes); + + nautilus_file_unref (file); + + update_node_without_reporting (model, node); + report_node_inserted (model, node); +} + +void +fm_tree_model_remove_root_uri (FMTreeModel *model, const char *uri) +{ + TreeNode *node; + GtkTreePath *path; + FMTreeModelRoot *root; + NautilusFile *file; + + file = nautilus_file_get (uri); + for (node = model->details->root_node; node != NULL; node = node->next) { + if (file == node->file) { + break; + } + } + nautilus_file_unref (file); + + if (node) { + /* remove the node */ + nautilus_file_monitor_remove (node->file, model); + path = get_node_path (model, node); + + if (node->prev) { + node->prev->next = node->next; + } + if (node->next) { + node->next->prev = node->prev; + } + if (node == model->details->root_node) { + model->details->root_node = node->next; + } + + /* destroy the root identifier */ + root = node->root; + destroy_node_without_reporting (model, node); + g_hash_table_destroy (root->file_to_node_map); + g_free (root); + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + gtk_tree_path_free (path); + } +} + +FMTreeModel * +fm_tree_model_new (void) +{ + FMTreeModel *model; + + model = g_object_new (FM_TYPE_TREE_MODEL, NULL); + + return model; +} + +static void +set_theme (TreeNode *node, FMTreeModel *model) +{ + TreeNode *child; + + tree_node_update_closed_pixbuf (node); + tree_node_update_open_pixbuf (node); + + report_node_contents_changed (model, node); + + for (child = node->first_child; child != NULL; child = child->next) { + set_theme (child, model); + } +} + +void +fm_tree_model_set_theme (FMTreeModel *model) +{ + TreeNode *node; + + g_return_if_fail (FM_IS_TREE_MODEL (model)); + + node = model->details->root_node; + while (node != NULL) { + set_theme (node, model); + node = node->next; + } +} + + +void +fm_tree_model_set_show_hidden_files (FMTreeModel *model, + gboolean show_hidden_files) +{ + g_return_if_fail (FM_IS_TREE_MODEL (model)); + g_return_if_fail (show_hidden_files == FALSE || show_hidden_files == TRUE); + + show_hidden_files = show_hidden_files != FALSE; + if (model->details->show_hidden_files == show_hidden_files) { + return; + } + model->details->show_hidden_files = show_hidden_files; + stop_monitoring (model); + if (!show_hidden_files) { + destroy_by_function (model, nautilus_file_is_hidden_file); + } + schedule_monitoring_update (model); +} + +void +fm_tree_model_set_show_backup_files (FMTreeModel *model, + gboolean show_backup_files) +{ + g_return_if_fail (FM_IS_TREE_MODEL (model)); + g_return_if_fail (show_backup_files == FALSE || show_backup_files == TRUE); + + show_backup_files = show_backup_files != FALSE; + if (model->details->show_backup_files == show_backup_files) { + return; + } + model->details->show_backup_files = show_backup_files; + stop_monitoring (model); + if (!show_backup_files) { + destroy_by_function (model, nautilus_file_is_backup_file); + } + schedule_monitoring_update (model); +} + +static gboolean +file_is_not_directory (NautilusFile *file) +{ + return !nautilus_file_is_directory (file); +} + +void +fm_tree_model_set_show_only_directories (FMTreeModel *model, + gboolean show_only_directories) +{ + g_return_if_fail (FM_IS_TREE_MODEL (model)); + g_return_if_fail (show_only_directories == FALSE || show_only_directories == TRUE); + + show_only_directories = show_only_directories != FALSE; + if (model->details->show_only_directories == show_only_directories) { + return; + } + model->details->show_only_directories = show_only_directories; + stop_monitoring (model); + if (show_only_directories) { + destroy_by_function (model, file_is_not_directory); + } + schedule_monitoring_update (model); +} + +NautilusFile * +fm_tree_model_iter_get_file (FMTreeModel *model, GtkTreeIter *iter) +{ + TreeNode *node; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), 0); + g_return_val_if_fail (iter_is_valid (FM_TREE_MODEL (model), iter), 0); + + node = iter->user_data; + return node == NULL ? NULL : nautilus_file_ref (node->file); +} + +gboolean +fm_tree_model_iter_is_root (FMTreeModel *model, GtkTreeIter *iter) +{ + TreeNode *node; + + g_return_val_if_fail (FM_IS_TREE_MODEL (model), 0); + g_return_val_if_fail (iter_is_valid (model, iter), 0); + node = iter->user_data; + if (node == NULL) { + return FALSE; + } else { + return (node->parent == NULL); + } +} + +gboolean +fm_tree_model_file_get_iter (FMTreeModel *model, + GtkTreeIter *iter, + NautilusFile *file, + GtkTreeIter *current_iter) +{ + TreeNode *node, *root_node; + + if (current_iter != NULL && current_iter->user_data != NULL) { + node = get_node_from_file (((TreeNode *) current_iter->user_data)->root, file); + return make_iter_for_node (node, iter, model->details->stamp); + } + + for (root_node = model->details->root_node; root_node != NULL; root_node = root_node->next) { + node = get_node_from_file (root_node->root, file); + if (node != NULL) { + return make_iter_for_node (node, iter, model->details->stamp); + } + } + return FALSE; +} + +static void +fm_tree_model_init (FMTreeModel *model) +{ + model->details = g_new0 (FMTreeModelDetails, 1); + + do { + model->details->stamp = g_random_int (); + } while (model->details->stamp == 0); +} + +static void +fm_tree_model_finalize (GObject *object) +{ + FMTreeModel *model; + TreeNode *root_node, *next_root; + FMTreeModelRoot *root; + + model = FM_TREE_MODEL (object); + + for (root_node = model->details->root_node; root_node != NULL; root_node = next_root) { + next_root = root_node->next; + root = root_node->root; + g_signal_handler_disconnect (root_node->file, root->changed_handler_id); + nautilus_file_monitor_remove (root_node->file, model); + destroy_node_without_reporting (model, root_node); + g_hash_table_destroy (root->file_to_node_map); + g_free (root); + } + + if (model->details->monitoring_update_idle_id != 0) { + g_source_remove (model->details->monitoring_update_idle_id); + } + + g_free (model->details); + + parent_class->finalize (object); +} + +static void +fm_tree_model_class_init (FMTreeModelClass *class) +{ + parent_class = g_type_class_peek_parent (class); + + G_OBJECT_CLASS (class)->finalize = fm_tree_model_finalize; + + tree_model_signals[ROW_LOADED] = + g_signal_new ("row_loaded", + FM_TYPE_TREE_MODEL, + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (FMTreeModelClass, row_loaded), + NULL, NULL, + g_cclosure_marshal_VOID__BOXED, + G_TYPE_NONE, 1, + GTK_TYPE_TREE_ITER); +} + +static void +fm_tree_model_tree_model_init (GtkTreeModelIface *iface) +{ + iface->get_flags = fm_tree_model_get_flags; + iface->get_n_columns = fm_tree_model_get_n_columns; + iface->get_column_type = fm_tree_model_get_column_type; + iface->get_iter = fm_tree_model_get_iter; + iface->get_path = fm_tree_model_get_path; + iface->get_value = fm_tree_model_get_value; + iface->iter_next = fm_tree_model_iter_next; + iface->iter_children = fm_tree_model_iter_children; + iface->iter_has_child = fm_tree_model_iter_has_child; + iface->iter_n_children = fm_tree_model_iter_n_children; + iface->iter_nth_child = fm_tree_model_iter_nth_child; + iface->iter_parent = fm_tree_model_iter_parent; + iface->ref_node = fm_tree_model_ref_node; + iface->unref_node = fm_tree_model_unref_node; +} + +GType +fm_tree_model_get_type (void) +{ + static GType object_type = 0; + + if (object_type == 0) { + static const GTypeInfo object_info = { + sizeof (FMTreeModelClass), + NULL, + NULL, + (GClassInitFunc) fm_tree_model_class_init, + NULL, + NULL, + sizeof (FMTreeModel), + 0, + (GInstanceInitFunc) fm_tree_model_init, + }; + + static const GInterfaceInfo tree_model_info = { + (GInterfaceInitFunc) fm_tree_model_tree_model_init, + NULL, + NULL + }; + + object_type = g_type_register_static (G_TYPE_OBJECT, "FMTreeModel", &object_info, 0); + g_type_add_interface_static (object_type, + GTK_TYPE_TREE_MODEL, + &tree_model_info); + } + + return object_type; +} diff --git a/src/file-manager/fm-tree-model.h b/src/file-manager/fm-tree-model.h new file mode 100644 index 000000000..5d7146de5 --- /dev/null +++ b/src/file-manager/fm-tree-model.h @@ -0,0 +1,88 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Copyright (C) 2002 Anders Carlsson + * Copyright (C) 2002 Bent Spoon Software + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Anders Carlsson <andersca@gnu.org> + */ + +/* fm-tree-model.h - Model for the tree view */ + +#ifndef FM_TREE_MODEL_H +#define FM_TREE_MODEL_H + +#include <glib-object.h> +#include <gtk/gtktreemodel.h> +#include <libnautilus-private/nautilus-file.h> + +#define FM_TYPE_TREE_MODEL (fm_tree_model_get_type ()) +#define FM_TREE_MODEL(obj) (GTK_CHECK_CAST ((obj), FM_TYPE_TREE_MODEL, FMTreeModel)) +#define FM_TREE_MODEL_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), FM_TYPE_TREE_MODEL, FMTreeModelClass)) +#define FM_IS_TREE_MODEL(obj) (GTK_CHECK_TYPE ((obj), FM_TYPE_TREE_MODEL)) +#define FM_IS_TREE_MODEL_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), FM_TYPE_TREE_MODEL)) + +enum { + FM_TREE_MODEL_DISPLAY_NAME_COLUMN, + FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN, + FM_TREE_MODEL_OPEN_PIXBUF_COLUMN, + FM_TREE_MODEL_FONT_STYLE_COLUMN, + FM_TREE_MODEL_FONT_WEIGHT_COLUMN, + FM_TREE_MODEL_NUM_COLUMNS +}; + +typedef struct FMTreeModelDetails FMTreeModelDetails; + +typedef struct { + GObject parent; + FMTreeModelDetails *details; +} FMTreeModel; + +typedef struct { + GObjectClass parent_class; + + void (* row_loaded) (FMTreeModel *tree_model, + GtkTreeIter *iter); +} FMTreeModelClass; + +GType fm_tree_model_get_type (void); +FMTreeModel *fm_tree_model_new (void); +void fm_tree_model_set_show_hidden_files (FMTreeModel *model, + gboolean show_hidden_files); +void fm_tree_model_set_show_backup_files (FMTreeModel *model, + gboolean show_backup_files); +void fm_tree_model_set_show_only_directories (FMTreeModel *model, + gboolean show_only_directories); +NautilusFile * fm_tree_model_iter_get_file (FMTreeModel *model, + GtkTreeIter *iter); +void fm_tree_model_add_root_uri (FMTreeModel *model, + const char *root_uri, + const char *display_name, + const char *icon_name); +void fm_tree_model_remove_root_uri (FMTreeModel *model, + const char *root_uri); +gboolean fm_tree_model_iter_is_root (FMTreeModel *model, + GtkTreeIter *iter); +gboolean fm_tree_model_file_get_iter (FMTreeModel *model, + GtkTreeIter *iter, + NautilusFile *file, + GtkTreeIter *currentIter); + +void fm_tree_model_set_theme (FMTreeModel *model); + +#endif /* FM_TREE_MODEL_H */ diff --git a/src/file-manager/fm-tree-view.c b/src/file-manager/fm-tree-view.c new file mode 100644 index 000000000..9e556a1ab --- /dev/null +++ b/src/file-manager/fm-tree-view.c @@ -0,0 +1,1305 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Copyright (C) 2000, 2001 Eazel, Inc + * Copyright (C) 2002 Anders Carlsson + * Copyright (C) 2002 Darin Adler + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: + * Maciej Stachowiak <mjs@eazel.com> + * Anders Carlsson <andersca@gnu.org> + * Darin Adler <darin@bentspoon.com> + */ + +/* fm-tree-view.c - tree sidebar panel + */ + +#include <config.h> +#include "fm-tree-view.h" + +#include "fm-tree-model.h" +#include "fm-properties-window.h" +#include <eel/eel-glib-extensions.h> +#include <eel/eel-preferences.h> +#include <eel/eel-string.h> +#include <eel/eel-vfs-extensions.h> +#include <gtk/gtkcellrendererpixbuf.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtkscrolledwindow.h> +#include <gtk/gtktreemodelsort.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkimage.h> +#include <gtk/gtkimagemenuitem.h> +#include <gtk/gtkseparatormenuitem.h> +#include <gtk/gtkmenu.h> +#include <gtk/gtkmenushell.h> +#include <gtk/gtkclipboard.h> +#include <libgnome/gnome-i18n.h> +#include <libgnomeui/gnome-uidefs.h> +#include <libgnomeui/gnome-popup-menu.h> +#include <libgnomevfs/gnome-vfs-utils.h> +#include <libgnomevfs/gnome-vfs-volume-monitor.h> +#include <libnautilus-private/nautilus-file-attributes.h> +#include <libnautilus-private/nautilus-file-operations.h> +#include <libnautilus-private/nautilus-global-preferences.h> +#include <libnautilus-private/nautilus-program-choosing.h> +#include <libnautilus-private/nautilus-tree-view-drag-dest.h> +#include <libnautilus-private/nautilus-icon-factory.h> + +struct FMTreeViewDetails { + GtkWidget *scrolled_window; + GtkTreeView *tree_widget; + GtkTreeModelSort *sort_model; + FMTreeModel *child_model; + + NautilusFile *activation_file; + gboolean activation_in_new_window; + + NautilusTreeViewDragDest *drag_dest; + + char *selection_location; + gboolean selecting; + + guint show_selection_idle_id; + + GtkWidget *popup; + GtkWidget *popup_open; + GtkWidget *popup_open_in_new_window; + GtkWidget *popup_create_folder; + GtkWidget *popup_cut; + GtkWidget *popup_copy; + GtkWidget *popup_paste; + GtkWidget *popup_rename; + GtkWidget *popup_trash; + GtkWidget *popup_properties; + NautilusFile *popup_file; +}; + +typedef struct { + GList *uris; + FMTreeView *view; +} PrependURIParameters; + +static GdkAtom copied_files_atom; + +enum { + GNOME_COPIED_FILES +}; + +static const GtkTargetEntry clipboard_targets[] = { + { "x-special/gnome-copied-files", 0, GNOME_COPIED_FILES }, +}; + +BONOBO_CLASS_BOILERPLATE (FMTreeView, fm_tree_view, + NautilusView, NAUTILUS_TYPE_VIEW) + +static gboolean +show_iter_for_file (FMTreeView *view, NautilusFile *file, GtkTreeIter *iter) +{ + GtkTreeModel *model; + NautilusFile *parent_file; + GtkTreeIter parent_iter; + GtkTreePath *path, *sort_path; + GtkTreeIter cur_iter; + + if (view->details->child_model == NULL) { + return FALSE; + } + model = GTK_TREE_MODEL (view->details->child_model); + + /* check if file is visible in the same root as the currently selected folder is */ + gtk_tree_view_get_cursor (view->details->tree_widget, &path, NULL); + if (path != NULL) { + if (gtk_tree_model_get_iter (model, &cur_iter, path)) { + if (fm_tree_model_file_get_iter (view->details->child_model, + iter, file, &cur_iter)) { + return TRUE; + } + } + } + /* check if file is visible at all */ + if (fm_tree_model_file_get_iter (view->details->child_model, + iter, file, NULL)) { + return TRUE; + } + + parent_file = nautilus_file_get_parent (file); + + if (parent_file == NULL) { + return FALSE; + } + if (!show_iter_for_file (view, parent_file, &parent_iter)) { + nautilus_file_unref (parent_file); + return FALSE; + } + nautilus_file_unref (parent_file); + + if (parent_iter.user_data == NULL || parent_iter.stamp == 0) { + return FALSE; + } + path = gtk_tree_model_get_path (model, &parent_iter); + sort_path = gtk_tree_model_sort_convert_child_path_to_path + (view->details->sort_model, path); + gtk_tree_path_free (path); + gtk_tree_view_expand_row (view->details->tree_widget, sort_path, FALSE); + gtk_tree_path_free (sort_path); + + return FALSE; +} + +static gboolean +show_selection_idle_callback (gpointer callback_data) +{ + FMTreeView *view; + NautilusFile *file, *old_file; + GtkTreeIter iter; + GtkTreePath *path, *sort_path; + + view = FM_TREE_VIEW (callback_data); + + view->details->show_selection_idle_id = 0; + + file = nautilus_file_get (view->details->selection_location); + if (file == NULL) { + return FALSE; + } + + if (!nautilus_file_is_directory (file)) { + old_file = file; + file = nautilus_file_get_parent (file); + nautilus_file_unref (old_file); + if (file == NULL) { + return FALSE; + } + } + + view->details->selecting = TRUE; + if (!show_iter_for_file (view, file, &iter)) { + nautilus_file_unref (file); + return FALSE; + } + view->details->selecting = FALSE; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->details->child_model), &iter); + sort_path = gtk_tree_model_sort_convert_child_path_to_path + (view->details->sort_model, path); + gtk_tree_path_free (path); + gtk_tree_view_set_cursor (view->details->tree_widget, sort_path, NULL, FALSE); + gtk_tree_view_scroll_to_cell (view->details->tree_widget, sort_path, NULL, FALSE, 0, 0); + gtk_tree_path_free (sort_path); + + nautilus_file_unref (file); + + return FALSE; +} + +static void +schedule_show_selection (FMTreeView *view) +{ + if (view->details->show_selection_idle_id == 0) { + view->details->show_selection_idle_id = g_idle_add (show_selection_idle_callback, view); + } +} + +static void +row_loaded_callback (GtkTreeModel *tree_model, + GtkTreeIter *iter, + FMTreeView *view) +{ + NautilusFile *file, *tmp_file, *selection_file; + + if (view->details->selection_location == NULL + || !view->details->selecting + || iter->user_data == NULL || iter->stamp == 0) { + return; + } + + file = fm_tree_model_iter_get_file (view->details->child_model, iter); + if (file == NULL) { + return; + } + if (!nautilus_file_is_directory (file)) { + nautilus_file_unref(file); + return; + } + + /* if iter is ancestor of wanted selection_location then update selection */ + selection_file = nautilus_file_get (view->details->selection_location); + while (selection_file != NULL) { + if (file == selection_file) { + nautilus_file_unref (file); + nautilus_file_unref (selection_file); + + schedule_show_selection (view); + return; + } + tmp_file = nautilus_file_get_parent (selection_file); + nautilus_file_unref (selection_file); + selection_file = tmp_file; + } + nautilus_file_unref (file); +} + +static NautilusFile * +sort_model_iter_to_file (FMTreeView *view, GtkTreeIter *iter) +{ + GtkTreeIter child_iter; + + gtk_tree_model_sort_convert_iter_to_child_iter (view->details->sort_model, &child_iter, iter); + return fm_tree_model_iter_get_file (view->details->child_model, &child_iter); +} + +static NautilusFile * +sort_model_path_to_file (FMTreeView *view, GtkTreePath *path) +{ + GtkTreeIter iter; + + if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (view->details->sort_model), &iter, path)) { + return NULL; + } + return sort_model_iter_to_file (view, &iter); +} + +static void +got_activation_uri_callback (NautilusFile *file, gpointer callback_data) +{ + char *uri, *file_uri; + FMTreeView *view; + GdkScreen *screen; + Nautilus_ViewFrame_OpenMode mode; + + view = FM_TREE_VIEW (callback_data); + + screen = gtk_widget_get_screen (GTK_WIDGET (view->details->tree_widget)); + + g_assert (file == view->details->activation_file); + + mode = view->details->activation_in_new_window ? Nautilus_ViewFrame_OPEN_IN_NAVIGATION : Nautilus_ViewFrame_OPEN_ACCORDING_TO_MODE; + + /* FIXME: reenable && !eel_uris_match_ignore_fragments (view->details->current_main_view_uri, uri) */ + + uri = nautilus_file_get_activation_uri (file); + if (uri != NULL + && eel_str_has_prefix (uri, NAUTILUS_COMMAND_SPECIFIER)) { + + uri += strlen (NAUTILUS_COMMAND_SPECIFIER); + nautilus_launch_application_from_command (screen, NULL, uri, NULL, FALSE); + + } else if (uri != NULL + && eel_str_has_prefix (uri, NAUTILUS_DESKTOP_COMMAND_SPECIFIER)) { + + file_uri = nautilus_file_get_uri (file); + nautilus_launch_desktop_file (screen, file_uri, NULL, NULL); + g_free (file_uri); + + } else if (uri != NULL + && nautilus_file_is_executable (file) + && nautilus_file_can_execute (file) + && !nautilus_file_is_directory (file)) { + + file_uri = gnome_vfs_get_local_path_from_uri (uri); + + /* Non-local executables don't get launched. They act like non-executables. */ + if (file_uri == NULL) { + nautilus_view_open_location + (NAUTILUS_VIEW (view), + uri, + mode, + 0, + NULL); + } else { + nautilus_launch_application_from_command (screen, NULL, file_uri, NULL, FALSE); + g_free (file_uri); + } + + } else if (uri != NULL) { + if (view->details->selection_location == NULL || + strcmp (uri, view->details->selection_location) != 0) { + if (view->details->selection_location != NULL) { + g_free (view->details->selection_location); + } + view->details->selection_location = g_strdup (uri); + nautilus_view_open_location + (NAUTILUS_VIEW (view), + uri, + mode, + 0, + NULL); + } + } + + g_free (uri); + nautilus_file_unref (view->details->activation_file); + view->details->activation_file = NULL; +} + +static void +cancel_activation (FMTreeView *view) +{ + if (view->details->activation_file == NULL) { + return; + } + + nautilus_file_cancel_call_when_ready + (view->details->activation_file, + got_activation_uri_callback, view); + nautilus_file_unref (view->details->activation_file); + view->details->activation_file = NULL; +} + +static void +row_activated_callback (GtkTreeView *treeview, GtkTreePath *path, + GtkTreeViewColumn *column, FMTreeView *view) +{ + if (gtk_tree_view_row_expanded (view->details->tree_widget, path)) { + gtk_tree_view_collapse_row (view->details->tree_widget, path); + } else { + gtk_tree_view_expand_row (view->details->tree_widget, + path, FALSE); + } +} + + +static void +selection_changed_callback (GtkTreeSelection *selection, + FMTreeView *view) +{ + NautilusFileAttributes attributes; + GtkTreeIter iter; + + /* no activation if popup menu is open */ + if (view->details->popup_file != NULL) { + return; + } + + cancel_activation (view); + + if (!gtk_tree_selection_get_selected (selection, NULL, &iter)) { + return; + } + + view->details->activation_file = sort_model_iter_to_file (view, &iter); + if (view->details->activation_file == NULL) { + return; + } + view->details->activation_in_new_window = FALSE; + + attributes = NAUTILUS_FILE_ATTRIBUTE_ACTIVATION_URI; + nautilus_file_call_when_ready (view->details->activation_file, attributes, + got_activation_uri_callback, view); +} + +static int +compare_rows (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer callback_data) +{ + NautilusFile *file_a, *file_b; + int result; + + if (a->user_data == NULL) { + return -1; + } + else if (b->user_data == NULL) { + return -1; + } + + /* don't sort root nodes */ + if (fm_tree_model_iter_is_root (FM_TREE_MODEL (model), a) + || fm_tree_model_iter_is_root (FM_TREE_MODEL (model), b)) { + return 0; + } + + file_a = fm_tree_model_iter_get_file (FM_TREE_MODEL (model), a); + file_b = fm_tree_model_iter_get_file (FM_TREE_MODEL (model), b); + + if (file_a == file_b) { + result = 0; + } else if (file_a == NULL) { + result = -1; + } else if (file_b == NULL) { + result = +1; + } else { + result = nautilus_file_compare_for_sort (file_a, file_b, + NAUTILUS_FILE_SORT_BY_DISPLAY_NAME, + FALSE, FALSE); + } + + nautilus_file_unref (file_a); + nautilus_file_unref (file_b); + + return result; +} + + +static char * +get_root_uri_callback (NautilusTreeViewDragDest *dest, + gpointer user_data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (user_data); + + /* Don't allow drops on background */ + return NULL; +} + +static NautilusFile * +get_file_for_path_callback (NautilusTreeViewDragDest *dest, + GtkTreePath *path, + gpointer user_data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (user_data); + + return sort_model_path_to_file (view, path); +} + +static void +move_copy_items_callback (NautilusTreeViewDragDest *dest, + const GList *item_uris, + const char *target_uri, + guint action, + int x, + int y, + gpointer user_data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (user_data); + + nautilus_file_operations_copy_move + (item_uris, + NULL, + target_uri, + action, + GTK_WIDGET (view->details->tree_widget), + NULL, NULL); +} + +static void +theme_changed_callback (GObject *icon_factory, gpointer callback_data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (callback_data); + if (view->details->child_model != NULL) { + fm_tree_model_set_theme (FM_TREE_MODEL (view->details->child_model)); + } +} + +static void +add_root_for_volume (FMTreeView *view, + GnomeVFSVolume *volume) +{ + char *icon, *mount_uri, *name; + + if (!gnome_vfs_volume_is_user_visible (volume)) { + return; + } + + icon = gnome_vfs_volume_get_icon (volume); + mount_uri = gnome_vfs_volume_get_activation_uri (volume); + name = gnome_vfs_volume_get_display_name (volume); + + fm_tree_model_add_root_uri (view->details->child_model, + mount_uri, name, icon); + + g_free (icon); + g_free (name); + g_free (mount_uri); + +} + +static void +volume_mounted_callback (GnomeVFSVolumeMonitor *volume_monitor, + GnomeVFSVolume *volume, + FMTreeView *view) +{ + add_root_for_volume (view, volume); +} + +static void +volume_unmounted_callback (GnomeVFSVolumeMonitor *volume_monitor, + GnomeVFSVolume *volume, + FMTreeView *view) +{ + char *mount_uri; + + mount_uri = gnome_vfs_volume_get_activation_uri (volume); + fm_tree_model_remove_root_uri (view->details->child_model, + mount_uri); + g_free (mount_uri); +} + +static void +clipboard_contents_received_callback (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + gpointer data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (data); + + if (selection_data->type == copied_files_atom + && selection_data->length > 0) { + gtk_widget_set_sensitive (view->details->popup_paste, TRUE); + } +} + +static GtkClipboard * +get_clipboard (GtkWidget *widget) +{ + return gtk_clipboard_get_for_display (gtk_widget_get_display (widget), + GDK_SELECTION_CLIPBOARD); +} + +static gboolean +can_move_uri_to_trash (const char *file_uri_string) +{ + /* Return TRUE if we can get a trash directory on the same volume as this file. */ + GnomeVFSURI *file_uri; + GnomeVFSURI *directory_uri; + GnomeVFSURI *trash_dir_uri; + gboolean result; + + g_return_val_if_fail (file_uri_string != NULL, FALSE); + + file_uri = gnome_vfs_uri_new (file_uri_string); + + if (file_uri == NULL) { + return FALSE; + } + + /* FIXME: Why can't we just pass file_uri to gnome_vfs_find_directory? */ + directory_uri = gnome_vfs_uri_get_parent (file_uri); + gnome_vfs_uri_unref (file_uri); + + if (directory_uri == NULL) { + return FALSE; + } + + /* + * Create a new trash if needed but don't go looking for an old Trash. + * Passing 0 permissions as gnome-vfs would override the permissions + * passed with 700 while creating .Trash directory + */ + result = gnome_vfs_find_directory (directory_uri, GNOME_VFS_DIRECTORY_KIND_TRASH, + &trash_dir_uri, TRUE, FALSE, 0) == GNOME_VFS_OK; + if (result) { + gnome_vfs_uri_unref (trash_dir_uri); + } + gnome_vfs_uri_unref (directory_uri); + + return result; +} + +static gboolean +button_pressed_callback (GtkTreeView *treeview, GdkEventButton *event, + FMTreeView *view) +{ + GtkTreePath *path, *cursor_path; + char *uri; + + if (event->button == 3) { + if (!gtk_tree_view_get_path_at_pos (treeview, event->x, event->y, + &path, NULL, NULL, NULL)) { + return FALSE; + } + + view->details->popup_file = sort_model_path_to_file (view, path); + if (view->details->popup_file == NULL) { + gtk_tree_path_free (path); + return FALSE; + } + gtk_tree_view_get_cursor (view->details->tree_widget, &cursor_path, NULL); + gtk_tree_view_set_cursor (view->details->tree_widget, path, NULL, FALSE); + gtk_tree_path_free (path); + uri = nautilus_file_get_uri (view->details->popup_file); + + gtk_widget_set_sensitive (view->details->popup_open_in_new_window, + nautilus_file_is_directory (view->details->popup_file)); + gtk_widget_set_sensitive (view->details->popup_create_folder, + nautilus_file_is_directory (view->details->popup_file) && + nautilus_file_can_write (view->details->popup_file)); + gtk_widget_set_sensitive (view->details->popup_paste, FALSE); + if (nautilus_file_is_directory (view->details->popup_file) && + nautilus_file_can_write (view->details->popup_file)) { + gtk_clipboard_request_contents (get_clipboard (GTK_WIDGET (view->details->tree_widget)), + copied_files_atom, + clipboard_contents_received_callback, view); + } + gtk_widget_set_sensitive (view->details->popup_trash, can_move_uri_to_trash (uri)); + g_free (uri); + + gnome_popup_menu_do_popup_modal (view->details->popup, + NULL, NULL, event, NULL, + GTK_WIDGET (treeview)); + + gtk_tree_view_set_cursor (view->details->tree_widget, cursor_path, NULL, FALSE); + gtk_tree_path_free (cursor_path); + + nautilus_file_unref (view->details->popup_file); + view->details->popup_file = NULL; + + return TRUE; + } + + return FALSE; +} + +static void +fm_tree_view_activate_file (FMTreeView *view, + NautilusFile *file, + gboolean open_in_new_window) +{ + NautilusFileAttributes attributes; + + cancel_activation (view); + + view->details->activation_file = nautilus_file_ref (file); + view->details->activation_in_new_window = open_in_new_window; + + attributes = NAUTILUS_FILE_ATTRIBUTE_ACTIVATION_URI; + nautilus_file_call_when_ready (view->details->activation_file, attributes, + got_activation_uri_callback, view); +} + +static void +fm_tree_view_open_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + fm_tree_view_activate_file (view, view->details->popup_file, FALSE); +} + +static void +fm_tree_view_open_in_new_window_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + fm_tree_view_activate_file (view, view->details->popup_file, TRUE); +} + +static void +new_folder_done (const char *new_folder_uri, gpointer data) +{ + GList *list; + + /* show the properties window for the newly created + * folder so the user can change its name + */ + list = g_list_prepend (NULL, nautilus_file_get (new_folder_uri)); + + fm_properties_window_present (list, GTK_WIDGET (data)); + + nautilus_file_list_free (list); +} + +static void +fm_tree_view_create_folder_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + char *parent_uri; + + parent_uri = nautilus_file_get_uri (view->details->popup_file); + nautilus_file_operations_new_folder (GTK_WIDGET (view->details->tree_widget), + parent_uri, + new_folder_done, view->details->tree_widget); + + g_free (parent_uri); +} + +static void +get_clipboard_callback (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + guint info, + gpointer user_data_or_owner) +{ + char *str = user_data_or_owner; + + gtk_selection_data_set (selection_data, + copied_files_atom, + 8, + str, + strlen (str)); +} + +static void +clear_clipboard_callback (GtkClipboard *clipboard, + gpointer user_data_or_owner) +{ + g_free (user_data_or_owner); +} + +static char * +convert_file_to_string (NautilusFile *file, + gboolean cut) +{ + GString *uris; + char *uri, *result; + + uris = g_string_new (cut ? "cut" : "copy"); + + uri = nautilus_file_get_uri (file); + g_string_append_c (uris, '\n'); + g_string_append (uris, uri); + g_free (uri); + + result = uris->str; + g_string_free (uris, FALSE); + + return result; +} + +static void +copy_or_cut_files (FMTreeView *view, + gboolean cut) +{ + char *status_string, *name; + char *clipboard_string; + + clipboard_string = convert_file_to_string (view->details->popup_file, cut); + + gtk_clipboard_set_with_data (get_clipboard (GTK_WIDGET (view->details->tree_widget)), + clipboard_targets, G_N_ELEMENTS (clipboard_targets), + get_clipboard_callback, clear_clipboard_callback, + clipboard_string); + + name = nautilus_file_get_display_name (view->details->popup_file); + if (cut) { + status_string = g_strdup_printf (_("\"%s\" will be moved " + "if you select the Paste Files command"), + name); + } else { + status_string = g_strdup_printf (_("\"%s\" will be copied " + "if you select the Paste Files command"), + name); + } + g_free (name); + + nautilus_view_report_status (NAUTILUS_VIEW (view), + status_string); + g_free (status_string); +} + +static void +fm_tree_view_cut_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + copy_or_cut_files (view, TRUE); +} + +static void +fm_tree_view_copy_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + copy_or_cut_files (view, FALSE); +} + +static GList * +convert_lines_to_str_list (char **lines, gboolean *cut) +{ + int i; + GList *result; + + if (lines[0] == NULL) { + return NULL; + } + + if (strcmp (lines[0], "cut") == 0) { + *cut = TRUE; + } else if (strcmp (lines[0], "copy") == 0) { + *cut = FALSE; + } else { + return NULL; + } + + result = NULL; + for (i = 1; lines[i] != NULL; i++) { + result = g_list_prepend (result, g_strdup (lines[i])); + } + return g_list_reverse (result); +} + +static void +paste_clipboard_data (FMTreeView *view, + GtkSelectionData *selection_data, + char *destination_uri) +{ + char **lines; + gboolean cut; + GList *item_uris; + + if (selection_data->type != copied_files_atom + || selection_data->length <= 0) { + item_uris = NULL; + } else { + /* Not sure why it's legal to assume there's an extra byte + * past the end of the selection data that it's safe to write + * to. But gtk_editable_selection_received does this, so I + * think it is OK. + */ + selection_data->data[selection_data->length] = '\0'; + lines = g_strsplit (selection_data->data, "\n", 0); + item_uris = convert_lines_to_str_list (lines, &cut); + g_strfreev (lines); + } + + if (item_uris == NULL|| destination_uri == NULL) { + nautilus_view_report_status (NAUTILUS_VIEW (view), + _("There is nothing on the clipboard to paste.")); + } else { + nautilus_file_operations_copy_move + (item_uris, NULL, destination_uri, + cut ? GDK_ACTION_MOVE : GDK_ACTION_COPY, + GTK_WIDGET (view->details->tree_widget), + NULL, NULL); + } +} + +static void +paste_into_clipboard_received_callback (GtkClipboard *clipboard, + GtkSelectionData *selection_data, + gpointer data) +{ + FMTreeView *view; + char *directory_uri; + + view = FM_TREE_VIEW (data); + + directory_uri = nautilus_file_get_uri (view->details->popup_file); + + paste_clipboard_data (view, selection_data, directory_uri); + + g_free (directory_uri); +} + +static void +fm_tree_view_paste_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + gtk_clipboard_request_contents (get_clipboard (GTK_WIDGET (view->details->tree_widget)), + copied_files_atom, + paste_into_clipboard_received_callback, view); +} + +static void +fm_tree_view_trash_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + GList *list; + char *directory_uri; + + directory_uri = nautilus_file_get_uri (view->details->popup_file); + + if (can_move_uri_to_trash (directory_uri)) + { + list = g_list_prepend (NULL, g_strdup (directory_uri)); + + nautilus_file_operations_copy_move (list, NULL, + EEL_TRASH_URI, GDK_ACTION_MOVE, GTK_WIDGET (view->details->tree_widget), + NULL, NULL); + } + + g_free (directory_uri); +} + +static void +fm_tree_view_properties_cb (GtkWidget *menu_item, + FMTreeView *view) +{ + GList *list; + + list = g_list_prepend (NULL, nautilus_file_ref (view->details->popup_file)); + + fm_properties_window_present (list, GTK_WIDGET (view->details->tree_widget)); + + nautilus_file_list_free (list); +} + +static void +create_popup_menu (FMTreeView *view) +{ + GtkWidget *popup, *menu_item, *menu_image, *separator_item; + + popup = gtk_menu_new (); + + /* add the "open" menu item */ + menu_image = gtk_image_new_from_stock (GTK_STOCK_OPEN, + GTK_ICON_SIZE_MENU); + gtk_widget_show (menu_image); + menu_item = gtk_image_menu_item_new_with_label (_("Open")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), + menu_image); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_open_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_open = menu_item; + + /* add the "open in new window" menu item */ + menu_item = gtk_image_menu_item_new_with_label (_("Open in New Window")); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_open_in_new_window_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_open_in_new_window = menu_item; + + separator_item = gtk_separator_menu_item_new (); + gtk_widget_show (separator_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), separator_item); + + /* add the "create folder" menu item */ + menu_item = gtk_image_menu_item_new_with_label (_("Create Folder")); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_create_folder_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_create_folder = menu_item; + + separator_item = gtk_separator_menu_item_new (); + gtk_widget_show (separator_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), separator_item); + + /* add the "cut folder" menu item */ + menu_image = gtk_image_new_from_stock (GTK_STOCK_CUT, + GTK_ICON_SIZE_MENU); + gtk_widget_show (menu_image); + menu_item = gtk_image_menu_item_new_with_label (_("Cut Folder")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), + menu_image); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_cut_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_cut = menu_item; + + /* add the "copy folder" menu item */ + menu_image = gtk_image_new_from_stock (GTK_STOCK_COPY, + GTK_ICON_SIZE_MENU); + gtk_widget_show (menu_image); + menu_item = gtk_image_menu_item_new_with_label (_("Copy Folder")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), + menu_image); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_copy_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_copy = menu_item; + + /* add the "paste files into folder" menu item */ + menu_image = gtk_image_new_from_stock (GTK_STOCK_PASTE, + GTK_ICON_SIZE_MENU); + gtk_widget_show (menu_image); + menu_item = gtk_image_menu_item_new_with_label (_("Paste Files into Folder")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), + menu_image); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_paste_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_paste = menu_item; + + separator_item = gtk_separator_menu_item_new (); + gtk_widget_show (separator_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), separator_item); + + /* add the "move to trash" menu item */ + menu_image = gtk_image_new_from_stock (GTK_STOCK_DELETE, + GTK_ICON_SIZE_MENU); + gtk_widget_show (menu_image); + menu_item = gtk_image_menu_item_new_with_label (_("Move to Trash")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), + menu_image); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_trash_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_trash = menu_item; + + separator_item = gtk_separator_menu_item_new (); + gtk_widget_show (separator_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), separator_item); + + /* add the "properties" menu item */ + menu_image = gtk_image_new_from_stock (GTK_STOCK_PROPERTIES, + GTK_ICON_SIZE_MENU); + gtk_widget_show (menu_image); + menu_item = gtk_image_menu_item_new_with_label (_("Properties")); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (menu_item), + menu_image); + g_signal_connect (menu_item, "activate", + G_CALLBACK (fm_tree_view_properties_cb), + view); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), menu_item); + view->details->popup_properties = menu_item; + + view->details->popup = popup; +} + +static void +create_tree (FMTreeView *view) +{ + GtkCellRenderer *cell; + GtkTreeViewColumn *column; + GnomeVFSVolumeMonitor *volume_monitor; + char *home_uri; + GList *volumes, *l; + + view->details->child_model = fm_tree_model_new (); + view->details->sort_model = GTK_TREE_MODEL_SORT + (gtk_tree_model_sort_new_with_model (GTK_TREE_MODEL (view->details->child_model))); + view->details->tree_widget = GTK_TREE_VIEW + (gtk_tree_view_new_with_model (GTK_TREE_MODEL (view->details->sort_model))); + g_object_unref (view->details->sort_model); + g_signal_connect_object + (view->details->child_model, "row_loaded", + G_CALLBACK (row_loaded_callback), + view, G_CONNECT_AFTER); + home_uri = gnome_vfs_get_uri_from_local_path (g_get_home_dir ()); + fm_tree_model_add_root_uri (view->details->child_model, home_uri, _("Home Folder"), "gnome-home"); + g_free (home_uri); + fm_tree_model_add_root_uri (view->details->child_model, "file:///", _("Filesystem"), "gnome-folder"); +#ifdef NOT_YET_USABLE + fm_tree_model_add_root_uri (view->details->child_model, "network:///", _("Network Neighbourhood"), "gnome-fs-network"); +#endif + + volume_monitor = gnome_vfs_get_volume_monitor (); + volumes = gnome_vfs_volume_monitor_get_mounted_volumes (volume_monitor); + for (l = volumes; l != NULL; l = l->next) { + add_root_for_volume (view, l->data); + gnome_vfs_volume_unref (l->data); + } + g_list_free (volumes); + + g_signal_connect_object (volume_monitor, "volume_mounted", + G_CALLBACK (volume_mounted_callback), view, 0); + g_signal_connect_object (volume_monitor, "volume_unmounted", + G_CALLBACK (volume_unmounted_callback), view, 0); + + g_object_unref (view->details->child_model); + + gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (view->details->sort_model), + compare_rows, view, NULL); + + gtk_tree_view_set_headers_visible (view->details->tree_widget, FALSE); + + view->details->drag_dest = + nautilus_tree_view_drag_dest_new (view->details->tree_widget); + g_signal_connect_object (view->details->drag_dest, + "get_root_uri", + G_CALLBACK (get_root_uri_callback), + view, 0); + g_signal_connect_object (view->details->drag_dest, + "get_file_for_path", + G_CALLBACK (get_file_for_path_callback), + view, 0); + g_signal_connect_object (view->details->drag_dest, + "move_copy_items", + G_CALLBACK (move_copy_items_callback), + view, 0); + + /* Create column */ + column = gtk_tree_view_column_new (); + + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_set_attributes (column, cell, + "pixbuf", FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN, + "pixbuf_expander_closed", FM_TREE_MODEL_CLOSED_PIXBUF_COLUMN, + "pixbuf_expander_open", FM_TREE_MODEL_OPEN_PIXBUF_COLUMN, + NULL); + + cell = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_set_attributes (column, cell, + "text", FM_TREE_MODEL_DISPLAY_NAME_COLUMN, + "style", FM_TREE_MODEL_FONT_STYLE_COLUMN, + "weight", FM_TREE_MODEL_FONT_WEIGHT_COLUMN, + NULL); + + gtk_tree_view_append_column (view->details->tree_widget, column); + + gtk_widget_show (GTK_WIDGET (view->details->tree_widget)); + + gtk_container_add (GTK_CONTAINER (view->details->scrolled_window), + GTK_WIDGET (view->details->tree_widget)); + + g_signal_connect_object (gtk_tree_view_get_selection (GTK_TREE_VIEW (view->details->tree_widget)), "changed", + G_CALLBACK (selection_changed_callback), view, 0); + + g_signal_connect (G_OBJECT (view->details->tree_widget), + "row-activated", G_CALLBACK (row_activated_callback), + view); + + g_signal_connect (G_OBJECT (view->details->tree_widget), + "button_press_event", G_CALLBACK (button_pressed_callback), + view); + + schedule_show_selection (view); +} + +static void +update_filtering_from_preferences (FMTreeView *view) +{ + if (view->details->child_model == NULL) { + return; + } + + fm_tree_model_set_show_hidden_files + (view->details->child_model, + eel_preferences_get_boolean (NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES)); + fm_tree_model_set_show_backup_files + (view->details->child_model, + eel_preferences_get_boolean (NAUTILUS_PREFERENCES_SHOW_BACKUP_FILES)); + fm_tree_model_set_show_only_directories + (view->details->child_model, + eel_preferences_get_boolean (NAUTILUS_PREFERENCES_TREE_SHOW_ONLY_DIRECTORIES)); +} + +static void +tree_activate_callback (BonoboControl *control, gboolean activating, gpointer user_data) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (user_data); + + if (activating && view->details->tree_widget == NULL) { + create_tree (view); + update_filtering_from_preferences (view); + } +} + +static void +filtering_changed_callback (gpointer callback_data) +{ + update_filtering_from_preferences (FM_TREE_VIEW (callback_data)); +} + +static void +load_location_callback (FMTreeView *view, char *location) +{ + if (view->details->selection_location != NULL) { + g_free (view->details->selection_location); + } + view->details->selection_location = g_strdup (location); + + schedule_show_selection (view); +} + +static void +fm_tree_view_instance_init (FMTreeView *view) +{ + BonoboControl *control; + + view->details = g_new0 (FMTreeViewDetails, 1); + + view->details->scrolled_window = gtk_scrolled_window_new (NULL, NULL); + + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (view->details->scrolled_window), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + + gtk_widget_show (view->details->scrolled_window); + + control = bonobo_control_new (view->details->scrolled_window); + g_signal_connect_object (control, "activate", + G_CALLBACK (tree_activate_callback), view, 0); + + nautilus_view_construct_from_bonobo_control (NAUTILUS_VIEW (view), control); + + view->details->selection_location = NULL; + g_signal_connect_object (view, "load_location", + G_CALLBACK (load_location_callback), view, 0); + view->details->selecting = FALSE; + + eel_preferences_add_callback (NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES, + filtering_changed_callback, view); + eel_preferences_add_callback (NAUTILUS_PREFERENCES_SHOW_BACKUP_FILES, + filtering_changed_callback, view); + eel_preferences_add_callback (NAUTILUS_PREFERENCES_TREE_SHOW_ONLY_DIRECTORIES, + filtering_changed_callback, view); + + g_signal_connect_object (nautilus_icon_factory_get(), "icons_changed", + G_CALLBACK (theme_changed_callback), view, 0); + + view->details->popup_file = NULL; + create_popup_menu (view); +} + +static void +fm_tree_view_dispose (GObject *object) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (object); + + if (view->details->drag_dest) { + g_object_unref (view->details->drag_dest); + view->details->drag_dest = NULL; + } + + if (view->details->show_selection_idle_id) { + g_source_remove (view->details->show_selection_idle_id); + view->details->show_selection_idle_id = 0; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +fm_tree_view_finalize (GObject *object) +{ + FMTreeView *view; + + view = FM_TREE_VIEW (object); + + eel_preferences_remove_callback (NAUTILUS_PREFERENCES_SHOW_HIDDEN_FILES, + filtering_changed_callback, view); + eel_preferences_remove_callback (NAUTILUS_PREFERENCES_SHOW_BACKUP_FILES, + filtering_changed_callback, view); + eel_preferences_remove_callback (NAUTILUS_PREFERENCES_TREE_SHOW_ONLY_DIRECTORIES, + filtering_changed_callback, view); + + cancel_activation (view); + + if (view->details->selection_location != NULL) { + g_free (view->details->selection_location); + } + + g_free (view->details); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +fm_tree_view_class_init (FMTreeViewClass *class) +{ + G_OBJECT_CLASS (class)->dispose = fm_tree_view_dispose; + G_OBJECT_CLASS (class)->finalize = fm_tree_view_finalize; + + copied_files_atom = gdk_atom_intern ("x-special/gnome-copied-files", FALSE); +} diff --git a/src/file-manager/fm-tree-view.h b/src/file-manager/fm-tree-view.h new file mode 100644 index 000000000..31ec9da54 --- /dev/null +++ b/src/file-manager/fm-tree-view.h @@ -0,0 +1,53 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* + * Copyright (C) 2000, 2001 Eazel, Inc + * Copyright (C) 2002 Anders Carlsson + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Maciej Stachowiak <mjs@eazel.com> + * Anders Carlsson <andersca@gnu.org> + */ + +/* fm-tree-view.h - tree view. */ + + +#ifndef FM_TREE_VIEW_H +#define FM_TREE_VIEW_H + +#include <libnautilus/nautilus-view.h> + +#define FM_TYPE_TREE_VIEW (fm_tree_view_get_type ()) +#define FM_TREE_VIEW(obj) (GTK_CHECK_CAST ((obj), FM_TYPE_TREE_VIEW, FMTreeView)) +#define FM_TREE_VIEW_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), FM_TYPE_TREE_VIEW, FMTreeViewClass)) +#define FM_IS_TREE_VIEW(obj) (GTK_CHECK_TYPE ((obj), FM_TYPE_TREE_VIEW)) +#define FM_IS_TREE_VIEW_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), FM_TYPE_TREE_VIEW)) + +typedef struct FMTreeViewDetails FMTreeViewDetails; + +typedef struct { + NautilusView parent; + FMTreeViewDetails *details; +} FMTreeView; + +typedef struct { + NautilusViewClass parent_class; +} FMTreeViewClass; + +GType fm_tree_view_get_type (void); + +#endif /* FM_TREE_VIEW_H */ diff --git a/src/file-manager/nautilus-directory-view-ui.xml b/src/file-manager/nautilus-directory-view-ui.xml index 58f8c20ad..dbceac809 100644 --- a/src/file-manager/nautilus-directory-view-ui.xml +++ b/src/file-manager/nautilus-directory-view-ui.xml @@ -18,6 +18,9 @@ <cmd name="New Folder" _label="Create _Folder" _tip="Create a new empty folder inside this folder"/> + <cmd name="New Empty File" + _label="_Empty File" + _tip="Create a new empty file inside this folder"/> <cmd name="New Launcher" _label="Create L_auncher" _tip="Create a new launcher"/> @@ -92,6 +95,7 @@ <accel name="Delete" verb="Trash"/> <accel name="*Shift*KP_Delete" verb="Delete"/> <accel name="*Alt*Down" verb="Open"/> + <accel name="*Alt**Shift*Down" verb="OpenCloseParent"/> </keybindings> <menu> <submenu name="File"> @@ -100,6 +104,16 @@ <menuitem name="New Folder" accel="*Shift**Control*n" verb="New Folder"/> + <submenu name="New Documents" + _label="Create _Document"> + <menuitem name="No Templates" + _label="No templates Installed" + sensitive="0"/> + <placeholder name="New Documents Placeholder" delimit="none"/> + <separator name="After New Documents"/> + <menuitem name="New Empty File" + verb="New Empty File"/> + </submenu> <menuitem name="New Launcher" pixtype="stock" pixname="gtk-new" verb="New Launcher"/> @@ -210,6 +224,16 @@ pixtype="stock" pixname="gtk-new" verb="New Launcher"/> </placeholder> + <submenu name="New Documents" + _label="Create _Document"> + <menuitem name="No Templates" + _label="No templates Installed" + sensitive="0"/> + <placeholder name="New Documents Placeholder" delimit="none"/> + <separator name="After New Documents"/> + <menuitem name="New Empty File" + verb="New Empty File"/> + </submenu> <submenu name="Scripts" _label="_Scripts" _tip="Run or manage scripts from ~/Nautilus/scripts" diff --git a/src/file-manager/nautilus-indexing-info.c b/src/file-manager/nautilus-indexing-info.c index f570856a3..85e445e12 100644 --- a/src/file-manager/nautilus-indexing-info.c +++ b/src/file-manager/nautilus-indexing-info.c @@ -161,23 +161,18 @@ last_index_time_dialog_new (void) GtkWidget *label; GtkDialog *dialog; - dialog = eel_create_info_dialog (_("Once a day your files and text content are indexed so " + time_str = nautilus_indexing_info_get_last_index_time (); + label_str = g_strdup_printf (_("Your files were last indexed at %s."), + time_str); + g_free (time_str); + + dialog = eel_create_info_dialog (label_str, + _("Once a day your files and text content are indexed so " "your searches are fast. "), _("Indexing Status"), NULL); set_close_hides_for_dialog (dialog); - time_str = nautilus_indexing_info_get_last_index_time (); - label_str = g_strdup_printf (_("Your files were last indexed at %s"), - time_str); - g_free (time_str); - - label = gtk_label_new (label_str); - gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT); - eel_gtk_label_make_bold (GTK_LABEL (label)); - gtk_box_pack_start (GTK_BOX (dialog->vbox), label, - FALSE, FALSE, 0); - return dialog; } @@ -199,8 +194,9 @@ index_progress_dialog_new (void) ProgressChangeData *progress_data; guint timeout_id; - dialog = eel_create_info_dialog (_("Once a day your files and text content are indexed so " - "your searches are fast. Your files are currently being indexed."), + dialog = eel_create_info_dialog (_("Your files are currently being indexed."), + _("Once a day your files and text content are indexed so " + "your searches are fast."), _("Indexing Status"), NULL); set_close_hides_for_dialog (dialog); percentage_complete = get_index_percentage_complete (); @@ -258,11 +254,12 @@ show_indexing_info_dialog (void) if (!medusa_system_services_are_enabled ()) { details_string = nautilus_medusa_get_explanation_of_enabling (); - dialog_shown = eel_show_info_dialog_with_details (_("When Fast Search is enabled, Find creates an " + dialog_shown = eel_show_info_dialog_with_details (_("There is no index of your files right now."), + _("When Fast Search is enabled, Find creates an " "index to speed up searches. Fast searching " "is not enabled on your computer, so you " "do not have an index right now."), - _("There is no index of your files right now."), + _("No Index of Files"), details_string, NULL); g_free (details_string); @@ -300,6 +297,7 @@ static void show_search_service_not_available_dialog (void) { eel_show_error_dialog (_("Sorry, but the medusa search service is not available."), + _("Please verify medusa has been setup correctly."), _("Search Service Not Available"), NULL); } |