1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
6 * Copyright (C) 2000, 2001 Eazel, Inc.
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of the
11 * License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this library; if not, write to the Free Software
20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 * Author: Andy Hertzfeld <andy@eazel.com>
26 /* notes sidebar panel -- allows editing per-directory notes */
30 #include "nautilus-notes-viewer.h"
32 #include <eel/eel-debug.h>
33 #include <eel/eel-gtk-extensions.h>
34 #include <eel/eel-string.h>
35 #include <gtk/gtkmain.h>
36 #include <gtk/gtksignal.h>
37 #include <gtk/gtktextbuffer.h>
38 #include <gtk/gtktextview.h>
39 #include <gtk/gtkvbox.h>
40 #include <gtk/gtkscrolledwindow.h>
41 #include <glib/gi18n.h>
42 #include <libnautilus-private/nautilus-file-attributes.h>
43 #include <libnautilus-private/nautilus-file.h>
44 #include <libnautilus-private/nautilus-file-utilities.h>
45 #include <libnautilus-private/nautilus-global-preferences.h>
46 #include <libnautilus-private/nautilus-metadata.h>
47 #include <libnautilus-private/nautilus-clipboard.h>
48 #include <libnautilus-private/nautilus-module.h>
49 #include <libnautilus-private/nautilus-sidebar-provider.h>
50 #include <libnautilus-extension/nautilus-property-page-provider.h>
52 #define SAVE_TIMEOUT (3 * 1000)
54 static void load_note_text_from_metadata (NautilusFile
*file
,
55 NautilusNotesViewer
*notes
);
56 static void notes_save_metainfo (NautilusNotesViewer
*notes
);
57 static void nautilus_notes_viewer_sidebar_iface_init (NautilusSidebarIface
*iface
);
58 static void on_changed (GtkEditable
*editable
,
59 NautilusNotesViewer
*notes
);
60 static void property_page_provider_iface_init (NautilusPropertyPageProviderIface
*iface
);
61 static void sidebar_provider_iface_init (NautilusSidebarProviderIface
*iface
);
64 GtkScrolledWindowClass parent
;
65 } NautilusNotesViewerClass
;
69 } NautilusNotesViewerProvider
;
73 } NautilusNotesViewerProviderClass
;
76 G_DEFINE_TYPE_WITH_CODE (NautilusNotesViewer
, nautilus_notes_viewer
, GTK_TYPE_SCROLLED_WINDOW
,
77 G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_SIDEBAR
,
78 nautilus_notes_viewer_sidebar_iface_init
));
80 static GType
nautilus_notes_viewer_provider_get_type (void);
82 G_DEFINE_TYPE_WITH_CODE (NautilusNotesViewerProvider
, nautilus_notes_viewer_provider
, G_TYPE_OBJECT
,
83 G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_PROPERTY_PAGE_PROVIDER
,
84 property_page_provider_iface_init
);
85 G_IMPLEMENT_INTERFACE (NAUTILUS_TYPE_SIDEBAR_PROVIDER
,
86 sidebar_provider_iface_init
));
89 struct _NautilusNotesViewerDetails
{
90 GtkWidget
*note_text_field
;
91 GtkTextBuffer
*text_buffer
;
94 guint save_timeout_id
;
95 char *previous_saved_text
;
100 schedule_save_callback (gpointer data
)
102 NautilusNotesViewer
*notes
;
106 /* Zero out save_timeout_id so no one will try to cancel our
107 * in-progress timeout callback.
109 notes
->details
->save_timeout_id
= 0;
111 notes_save_metainfo (notes
);
117 cancel_pending_save (NautilusNotesViewer
*notes
)
119 if (notes
->details
->save_timeout_id
!= 0) {
120 g_source_remove (notes
->details
->save_timeout_id
);
121 notes
->details
->save_timeout_id
= 0;
126 schedule_save (NautilusNotesViewer
*notes
)
128 cancel_pending_save (notes
);
130 notes
->details
->save_timeout_id
= g_timeout_add (SAVE_TIMEOUT
, schedule_save_callback
, notes
);
133 /* notifies event listeners if the notes data actually changed */
135 set_saved_text (NautilusNotesViewer
*notes
, char *new_notes
)
139 old_text
= notes
->details
->previous_saved_text
;
140 notes
->details
->previous_saved_text
= new_notes
;
142 if (eel_strcmp (old_text
, new_notes
) != 0) {
143 g_signal_emit_by_name (NAUTILUS_SIDEBAR (notes
),
150 /* save the metainfo corresponding to the current uri, if any, into the text field */
152 notes_save_metainfo (NautilusNotesViewer
*notes
)
155 GtkTextIter start_iter
;
156 GtkTextIter end_iter
;
158 if (notes
->details
->file
== NULL
) {
162 cancel_pending_save (notes
);
164 /* Block the handler, so we don't respond to our own change.
166 g_signal_handlers_block_matched (notes
->details
->file
,
167 G_SIGNAL_MATCH_FUNC
| G_SIGNAL_MATCH_DATA
,
169 G_CALLBACK (load_note_text_from_metadata
),
172 gtk_text_buffer_get_start_iter (notes
->details
->text_buffer
, &start_iter
);
173 gtk_text_buffer_get_end_iter (notes
->details
->text_buffer
, &end_iter
);
174 notes_text
= gtk_text_buffer_get_text (notes
->details
->text_buffer
,
179 nautilus_file_set_metadata (notes
->details
->file
,
180 NAUTILUS_METADATA_KEY_ANNOTATION
,
183 g_signal_handlers_unblock_matched (notes
->details
->file
,
184 G_SIGNAL_MATCH_FUNC
| G_SIGNAL_MATCH_DATA
,
186 G_CALLBACK (load_note_text_from_metadata
),
189 set_saved_text (notes
, notes_text
);
193 load_note_text_from_metadata (NautilusFile
*file
,
194 NautilusNotesViewer
*notes
)
198 g_assert (NAUTILUS_IS_FILE (file
));
199 g_assert (notes
->details
->file
== file
);
201 saved_text
= nautilus_file_get_metadata (file
, NAUTILUS_METADATA_KEY_ANNOTATION
, "");
203 /* This fn is called for any change signal on the file, so make sure that the
204 * metadata has actually changed.
206 if (eel_strcmp (saved_text
, notes
->details
->previous_saved_text
) != 0) {
207 set_saved_text (notes
, saved_text
);
208 cancel_pending_save (notes
);
210 /* Block the handler, so we don't respond to our own change.
212 g_signal_handlers_block_matched (notes
->details
->text_buffer
,
213 G_SIGNAL_MATCH_FUNC
| G_SIGNAL_MATCH_DATA
,
215 G_CALLBACK (on_changed
),
217 gtk_text_buffer_set_text (notes
->details
->text_buffer
, saved_text
, -1);
218 g_signal_handlers_unblock_matched (notes
->details
->text_buffer
,
219 G_SIGNAL_MATCH_FUNC
| G_SIGNAL_MATCH_DATA
,
221 G_CALLBACK (on_changed
),
229 done_with_file (NautilusNotesViewer
*notes
)
231 cancel_pending_save (notes
);
233 if (notes
->details
->file
!= NULL
) {
234 nautilus_file_monitor_remove (notes
->details
->file
, notes
);
235 g_signal_handlers_disconnect_matched (notes
->details
->file
,
236 G_SIGNAL_MATCH_FUNC
| G_SIGNAL_MATCH_DATA
,
238 G_CALLBACK (load_note_text_from_metadata
),
240 nautilus_file_unref (notes
->details
->file
);
245 notes_load_metainfo (NautilusNotesViewer
*notes
)
247 NautilusFileAttributes attributes
;
249 done_with_file (notes
);
250 notes
->details
->file
= nautilus_file_get_by_uri (notes
->details
->uri
);
252 /* Block the handler, so we don't respond to our own change.
254 g_signal_handlers_block_matched (notes
->details
->text_buffer
,
255 G_SIGNAL_MATCH_FUNC
| G_SIGNAL_MATCH_DATA
,
257 G_CALLBACK (on_changed
),
259 gtk_text_buffer_set_text (notes
->details
->text_buffer
, "", -1);
260 g_signal_handlers_unblock_matched (notes
->details
->text_buffer
,
261 G_SIGNAL_MATCH_FUNC
| G_SIGNAL_MATCH_DATA
,
263 G_CALLBACK (on_changed
),
266 if (notes
->details
->file
== NULL
) {
270 attributes
= NAUTILUS_FILE_ATTRIBUTE_METADATA
;
271 nautilus_file_monitor_add (notes
->details
->file
, notes
, attributes
);
273 if (nautilus_file_check_if_ready (notes
->details
->file
, attributes
)) {
274 load_note_text_from_metadata (notes
->details
->file
, notes
);
277 g_signal_connect (notes
->details
->file
, "changed",
278 G_CALLBACK (load_note_text_from_metadata
), notes
);
282 loading_uri_callback (NautilusSidebar
*sidebar
,
283 const char *location
,
284 NautilusNotesViewer
*notes
)
286 if (strcmp (notes
->details
->uri
, location
) != 0) {
287 notes_save_metainfo (notes
);
288 g_free (notes
->details
->uri
);
289 notes
->details
->uri
= g_strdup (location
);
290 notes_load_metainfo (notes
);
295 on_text_field_focus_out_event (GtkWidget
*widget
,
296 GdkEventFocus
*event
,
297 gpointer callback_data
)
299 NautilusNotesViewer
*notes
;
301 notes
= callback_data
;
302 notes_save_metainfo (notes
);
307 on_changed (GtkEditable
*editable
, NautilusNotesViewer
*notes
)
309 schedule_save (notes
);
313 nautilus_notes_viewer_init (NautilusNotesViewer
*sidebar
)
316 NautilusNotesViewerDetails
*details
;
318 details
= g_new0 (NautilusNotesViewerDetails
, 1);
319 sidebar
->details
= details
;
321 details
->uri
= g_strdup ("");
323 image_path
= nautilus_pixmap_file ("note-indicator.png");
325 details
->icon
= gdk_pixbuf_new_from_file (image_path
, NULL
);
329 /* create the text container */
330 details
->text_buffer
= gtk_text_buffer_new (NULL
);
331 details
->note_text_field
= gtk_text_view_new_with_buffer (details
->text_buffer
);
333 gtk_text_view_set_editable (GTK_TEXT_VIEW (details
->note_text_field
), TRUE
);
334 gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (details
->note_text_field
),
336 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sidebar
),
338 GTK_POLICY_AUTOMATIC
);
339 gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sidebar
),
341 gtk_scrolled_window_set_hadjustment (GTK_SCROLLED_WINDOW (sidebar
), NULL
);
342 gtk_scrolled_window_set_vadjustment (GTK_SCROLLED_WINDOW (sidebar
), NULL
);
343 gtk_container_add (GTK_CONTAINER (sidebar
), details
->note_text_field
);
345 g_signal_connect (details
->note_text_field
, "focus_out_event",
346 G_CALLBACK (on_text_field_focus_out_event
), sidebar
);
347 g_signal_connect (details
->text_buffer
, "changed",
348 G_CALLBACK (on_changed
), sidebar
);
350 gtk_widget_show_all (GTK_WIDGET (sidebar
));
355 nautilus_notes_viewer_finalize (GObject
*object
)
357 NautilusNotesViewer
*sidebar
;
359 sidebar
= NAUTILUS_NOTES_VIEWER (object
);
361 done_with_file (sidebar
);
362 if (sidebar
->details
->icon
!= NULL
) {
363 g_object_unref (sidebar
->details
->icon
);
365 g_free (sidebar
->details
->uri
);
366 g_free (sidebar
->details
->previous_saved_text
);
367 g_free (sidebar
->details
);
369 G_OBJECT_CLASS (nautilus_notes_viewer_parent_class
)->finalize (object
);
374 nautilus_notes_viewer_class_init (NautilusNotesViewerClass
*class)
376 G_OBJECT_CLASS (class)->finalize
= nautilus_notes_viewer_finalize
;
380 nautilus_notes_viewer_get_sidebar_id (NautilusSidebar
*sidebar
)
382 return NAUTILUS_NOTES_SIDEBAR_ID
;
386 nautilus_notes_viewer_get_tab_label (NautilusSidebar
*sidebar
)
388 return g_strdup (_("Notes"));
392 nautilus_notes_viewer_get_tab_tooltip (NautilusSidebar
*sidebar
)
394 return g_strdup (_("Show Notes"));
398 nautilus_notes_viewer_get_tab_icon (NautilusSidebar
*sidebar
)
400 NautilusNotesViewer
*notes
;
402 notes
= NAUTILUS_NOTES_VIEWER (sidebar
);
404 if (notes
->details
->previous_saved_text
!= NULL
&&
405 notes
->details
->previous_saved_text
[0] != '\0') {
406 return g_object_ref (notes
->details
->icon
);
413 nautilus_notes_viewer_is_visible_changed (NautilusSidebar
*sidebar
,
420 nautilus_notes_viewer_sidebar_iface_init (NautilusSidebarIface
*iface
)
422 iface
->get_sidebar_id
= nautilus_notes_viewer_get_sidebar_id
;
423 iface
->get_tab_label
= nautilus_notes_viewer_get_tab_label
;
424 iface
->get_tab_tooltip
= nautilus_notes_viewer_get_tab_tooltip
;
425 iface
->get_tab_icon
= nautilus_notes_viewer_get_tab_icon
;
426 iface
->is_visible_changed
= nautilus_notes_viewer_is_visible_changed
;
430 nautilus_notes_viewer_set_parent_window (NautilusNotesViewer
*sidebar
,
431 NautilusWindowInfo
*window
)
433 g_signal_connect_object (window
, "loading_uri",
434 G_CALLBACK (loading_uri_callback
), sidebar
, 0);
436 g_free (sidebar
->details
->uri
);
437 sidebar
->details
->uri
= nautilus_window_info_get_current_location (window
);
438 notes_load_metainfo (sidebar
);
440 nautilus_clipboard_set_up_text_view
441 (GTK_TEXT_VIEW (sidebar
->details
->note_text_field
),
442 nautilus_window_info_get_ui_manager (window
));
445 static NautilusSidebar
*
446 nautilus_notes_viewer_create_sidebar (NautilusSidebarProvider
*provider
,
447 NautilusWindowInfo
*window
)
449 NautilusNotesViewer
*sidebar
;
451 sidebar
= g_object_new (nautilus_notes_viewer_get_type (), NULL
);
452 nautilus_notes_viewer_set_parent_window (sidebar
, window
);
453 g_object_ref (sidebar
);
454 gtk_object_sink (GTK_OBJECT (sidebar
));
456 return NAUTILUS_SIDEBAR (sidebar
);
459 /* nautilus_property_page_provider_get_pages
461 * This function is called by Nautilus when it wants property page
462 * items from the extension.
464 * This function is called in the main thread before a property page
465 * is shown, so it should return quickly.
467 * The function should return a GList of allocated NautilusPropertyPage
471 get_property_pages (NautilusPropertyPageProvider
*provider
,
475 NautilusPropertyPage
*page
;
476 NautilusFileInfo
*file
;
478 NautilusNotesViewer
*viewer
;
481 /* Only show the property page if 1 file is selected */
482 if (!files
|| files
->next
!= NULL
) {
488 file
= NAUTILUS_FILE_INFO (files
->data
);
489 uri
= nautilus_file_info_get_uri (file
);
491 viewer
= g_object_new (nautilus_notes_viewer_get_type (), NULL
);
492 g_free (viewer
->details
->uri
);
493 viewer
->details
->uri
= uri
;
494 notes_load_metainfo (viewer
);
496 page
= nautilus_property_page_new
497 ("NautilusNotesViewer::property_page",
498 gtk_label_new (_("Notes")),
499 GTK_WIDGET (viewer
));
500 pages
= g_list_append (pages
, page
);
506 property_page_provider_iface_init (NautilusPropertyPageProviderIface
*iface
)
508 iface
->get_pages
= get_property_pages
;
512 sidebar_provider_iface_init (NautilusSidebarProviderIface
*iface
)
514 iface
->create
= nautilus_notes_viewer_create_sidebar
;
518 nautilus_notes_viewer_provider_init (NautilusNotesViewerProvider
*sidebar
)
523 nautilus_notes_viewer_provider_class_init (NautilusNotesViewerProviderClass
*class)
528 nautilus_notes_viewer_register (void)
530 nautilus_module_add_type (nautilus_notes_viewer_provider_get_type ());