/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ /* * Nautilus * * Copyright (C) 2000, 2001 Eazel, Inc. * * This library 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 library 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 library; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * * Author: Andy Hertzfeld * */ /* notes sidebar panel -- allows editing per-directory notes */ #include #include "nautilus-notes-viewer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SAVE_TIMEOUT (3 * 1000) static void load_note_text_from_metadata (NautilusFile *file, NautilusNotesViewer *notes); static void notes_save_metainfo (NautilusNotesViewer *notes); static void nautilus_notes_viewer_sidebar_iface_init (NautilusSidebarIface *iface); static void on_changed (GtkEditable *editable, NautilusNotesViewer *notes); static void property_page_provider_iface_init (NautilusPropertyPageProviderIface *iface); static void sidebar_provider_iface_init (NautilusSidebarProviderIface *iface); typedef struct { GtkScrolledWindowClass parent; } NautilusNotesViewerClass; typedef struct { GObject parent; } NautilusNotesViewerProvider; typedef struct { GObjectClass parent; } NautilusNotesViewerProviderClass; G_DEFINE_TYPE_WITH_CODE (NautilusNotesViewer, nautilus_notes_viewer, GTK_TYPE_SCROLLED_WINDOW, G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_SIDEBAR, nautilus_notes_viewer_sidebar_iface_init)); static GType nautilus_notes_viewer_provider_get_type (void); G_DEFINE_TYPE_WITH_CODE (NautilusNotesViewerProvider, nautilus_notes_viewer_provider, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_PROPERTY_PAGE_PROVIDER, property_page_provider_iface_init); G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_SIDEBAR_PROVIDER, sidebar_provider_iface_init)); struct _NautilusNotesViewerDetails { GtkWidget *note_text_field; GtkTextBuffer *text_buffer; char *uri; NautilusFile *file; guint save_timeout_id; char *previous_saved_text; GdkPixbuf *icon; }; static gboolean schedule_save_callback (gpointer data) { NautilusNotesViewer *notes; notes = data; /* Zero out save_timeout_id so no one will try to cancel our * in-progress timeout callback. */ notes->details->save_timeout_id = 0; notes_save_metainfo (notes); return FALSE; } static void cancel_pending_save (NautilusNotesViewer *notes) { if (notes->details->save_timeout_id != 0) { g_source_remove (notes->details->save_timeout_id); notes->details->save_timeout_id = 0; } } static void schedule_save (NautilusNotesViewer *notes) { cancel_pending_save (notes); notes->details->save_timeout_id = g_timeout_add (SAVE_TIMEOUT, schedule_save_callback, notes); } /* notifies event listeners if the notes data actually changed */ static void set_saved_text (NautilusNotesViewer *notes, char *new_notes) { char *old_text; old_text = notes->details->previous_saved_text; notes->details->previous_saved_text = new_notes; if (eel_strcmp (old_text, new_notes) != 0) { g_signal_emit_by_name (NAUTILUS_SIDEBAR (notes), "tab_icon_changed"); } g_free (old_text); } /* save the metainfo corresponding to the current uri, if any, into the text field */ static void notes_save_metainfo (NautilusNotesViewer *notes) { char *notes_text; GtkTextIter start_iter; GtkTextIter end_iter; if (notes->details->file == NULL) { return; } cancel_pending_save (notes); /* Block the handler, so we don't respond to our own change. */ g_signal_handlers_block_matched (notes->details->file, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, G_CALLBACK (load_note_text_from_metadata), notes); gtk_text_buffer_get_start_iter (notes->details->text_buffer, &start_iter); gtk_text_buffer_get_end_iter (notes->details->text_buffer, &end_iter); notes_text = gtk_text_buffer_get_text (notes->details->text_buffer, &start_iter, &end_iter, FALSE); nautilus_file_set_metadata (notes->details->file, NAUTILUS_METADATA_KEY_ANNOTATION, NULL, notes_text); g_signal_handlers_unblock_matched (notes->details->file, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, G_CALLBACK (load_note_text_from_metadata), notes); set_saved_text (notes, notes_text); } static void load_note_text_from_metadata (NautilusFile *file, NautilusNotesViewer *notes) { char *saved_text; g_assert (NAUTILUS_IS_FILE (file)); g_assert (notes->details->file == file); saved_text = nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_ANNOTATION, ""); /* This fn is called for any change signal on the file, so make sure that the * metadata has actually changed. */ if (eel_strcmp (saved_text, notes->details->previous_saved_text) != 0) { set_saved_text (notes, saved_text); cancel_pending_save (notes); /* Block the handler, so we don't respond to our own change. */ g_signal_handlers_block_matched (notes->details->text_buffer, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, G_CALLBACK (on_changed), notes); gtk_text_buffer_set_text (notes->details->text_buffer, saved_text, -1); g_signal_handlers_unblock_matched (notes->details->text_buffer, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, G_CALLBACK (on_changed), notes); } else { g_free (saved_text); } } static void done_with_file (NautilusNotesViewer *notes) { cancel_pending_save (notes); if (notes->details->file != NULL) { nautilus_file_monitor_remove (notes->details->file, notes); g_signal_handlers_disconnect_matched (notes->details->file, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, G_CALLBACK (load_note_text_from_metadata), notes); nautilus_file_unref (notes->details->file); } } static void notes_load_metainfo (NautilusNotesViewer *notes) { NautilusFileAttributes attributes; done_with_file (notes); notes->details->file = nautilus_file_get_by_uri (notes->details->uri); /* Block the handler, so we don't respond to our own change. */ g_signal_handlers_block_matched (notes->details->text_buffer, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, G_CALLBACK (on_changed), notes); gtk_text_buffer_set_text (notes->details->text_buffer, "", -1); g_signal_handlers_unblock_matched (notes->details->text_buffer, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, G_CALLBACK (on_changed), notes); if (notes->details->file == NULL) { return; } attributes = NAUTILUS_FILE_ATTRIBUTE_METADATA; nautilus_file_monitor_add (notes->details->file, notes, attributes); if (nautilus_file_check_if_ready (notes->details->file, attributes)) { load_note_text_from_metadata (notes->details->file, notes); } g_signal_connect (notes->details->file, "changed", G_CALLBACK (load_note_text_from_metadata), notes); } static void loading_uri_callback (NautilusSidebar *sidebar, const char *location, NautilusNotesViewer *notes) { if (strcmp (notes->details->uri, location) != 0) { notes_save_metainfo (notes); g_free (notes->details->uri); notes->details->uri = g_strdup (location); notes_load_metainfo (notes); } } static gboolean on_text_field_focus_out_event (GtkWidget *widget, GdkEventFocus *event, gpointer callback_data) { NautilusNotesViewer *notes; notes = callback_data; notes_save_metainfo (notes); return FALSE; } static void on_changed (GtkEditable *editable, NautilusNotesViewer *notes) { schedule_save (notes); } static void nautilus_notes_viewer_init (NautilusNotesViewer *sidebar) { char *image_path; NautilusNotesViewerDetails *details; details = g_new0 (NautilusNotesViewerDetails, 1); sidebar->details = details; details->uri = g_strdup (""); image_path = nautilus_pixmap_file ("note-indicator.png"); if (image_path) { details->icon = gdk_pixbuf_new_from_file (image_path, NULL); g_free (image_path); } /* create the text container */ details->text_buffer = gtk_text_buffer_new (NULL); details->note_text_field = gtk_text_view_new_with_buffer (details->text_buffer); gtk_text_view_set_editable (GTK_TEXT_VIEW (details->note_text_field), TRUE); gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (details->note_text_field), GTK_WRAP_WORD); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sidebar), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sidebar), GTK_SHADOW_IN); gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (sidebar), NULL); gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (sidebar), NULL); gtk_container_add (GTK_CONTAINER (sidebar), details->note_text_field); g_signal_connect (details->note_text_field, "focus_out_event", G_CALLBACK (on_text_field_focus_out_event), sidebar); g_signal_connect (details->text_buffer, "changed", G_CALLBACK (on_changed), sidebar); gtk_widget_show_all (GTK_WIDGET (sidebar)); } static void nautilus_notes_viewer_finalize (GObject *object) { NautilusNotesViewer *sidebar; sidebar = NAUTILUS_NOTES_VIEWER (object); done_with_file (sidebar); if (sidebar->details->icon != NULL) { g_object_unref (sidebar->details->icon); } g_free (sidebar->details->uri); g_free (sidebar->details->previous_saved_text); g_free (sidebar->details); G_OBJECT_CLASS (nautilus_notes_viewer_parent_class)->finalize (object); } static void nautilus_notes_viewer_class_init (NautilusNotesViewerClass *class) { G_OBJECT_CLASS (class)->finalize = nautilus_notes_viewer_finalize; } static const char * nautilus_notes_viewer_get_sidebar_id (NautilusSidebar *sidebar) { return NAUTILUS_NOTES_SIDEBAR_ID; } static char * nautilus_notes_viewer_get_tab_label (NautilusSidebar *sidebar) { return g_strdup (_("Notes")); } static char * nautilus_notes_viewer_get_tab_tooltip (NautilusSidebar *sidebar) { return g_strdup (_("Show Notes")); } static GdkPixbuf * nautilus_notes_viewer_get_tab_icon (NautilusSidebar *sidebar) { NautilusNotesViewer *notes; notes = NAUTILUS_NOTES_VIEWER (sidebar); if (notes->details->previous_saved_text != NULL && notes->details->previous_saved_text[0] != '\0') { return g_object_ref (notes->details->icon); } return NULL; } static void nautilus_notes_viewer_is_visible_changed (NautilusSidebar *sidebar, gboolean is_visible) { /* Do nothing */ } static void nautilus_notes_viewer_sidebar_iface_init (NautilusSidebarIface *iface) { iface->get_sidebar_id = nautilus_notes_viewer_get_sidebar_id; iface->get_tab_label = nautilus_notes_viewer_get_tab_label; iface->get_tab_tooltip = nautilus_notes_viewer_get_tab_tooltip; iface->get_tab_icon = nautilus_notes_viewer_get_tab_icon; iface->is_visible_changed = nautilus_notes_viewer_is_visible_changed; } static void nautilus_notes_viewer_set_parent_window (NautilusNotesViewer *sidebar, NautilusWindowInfo *window) { g_signal_connect_object (window, "loading_uri", G_CALLBACK (loading_uri_callback), sidebar, 0); g_free (sidebar->details->uri); sidebar->details->uri = nautilus_window_info_get_current_location (window); notes_load_metainfo (sidebar); nautilus_clipboard_set_up_text_view (GTK_TEXT_VIEW (sidebar->details->note_text_field), nautilus_window_info_get_ui_manager (window)); } static NautilusSidebar * nautilus_notes_viewer_create_sidebar (NautilusSidebarProvider *provider, NautilusWindowInfo *window) { NautilusNotesViewer *sidebar; sidebar = g_object_new (nautilus_notes_viewer_get_type (), NULL); nautilus_notes_viewer_set_parent_window (sidebar, window); g_object_ref (sidebar); gtk_object_sink (GTK_OBJECT (sidebar)); return NAUTILUS_SIDEBAR (sidebar); } /* nautilus_property_page_provider_get_pages * * This function is called by Nautilus when it wants property page * items from the extension. * * This function is called in the main thread before a property page * is shown, so it should return quickly. * * The function should return a GList of allocated NautilusPropertyPage * items. */ static GList * get_property_pages (NautilusPropertyPageProvider *provider, GList *files) { GList *pages; NautilusPropertyPage *page; NautilusFileInfo *file; char *uri; NautilusNotesViewer *viewer; /* Only show the property page if 1 file is selected */ if (!files || files->next != NULL) { return NULL; } pages = NULL; file = NAUTILUS_FILE_INFO (files->data); uri = nautilus_file_info_get_uri (file); viewer = g_object_new (nautilus_notes_viewer_get_type (), NULL); g_free (viewer->details->uri); viewer->details->uri = uri; notes_load_metainfo (viewer); page = nautilus_property_page_new ("NautilusNotesViewer::property_page", gtk_label_new (_("Notes")), GTK_WIDGET (viewer)); pages = g_list_append (pages, page); return pages; } static void property_page_provider_iface_init (NautilusPropertyPageProviderIface *iface) { iface->get_pages = get_property_pages; } static void sidebar_provider_iface_init (NautilusSidebarProviderIface *iface) { iface->create = nautilus_notes_viewer_create_sidebar; } static void nautilus_notes_viewer_provider_init (NautilusNotesViewerProvider *sidebar) { } static void nautilus_notes_viewer_provider_class_init (NautilusNotesViewerProviderClass *class) { } void nautilus_notes_viewer_register (void) { nautilus_module_add_type (nautilus_notes_viewer_provider_get_type ()); }