diff options
67 files changed, 9486 insertions, 361 deletions
@@ -1,3 +1,168 @@ +2001-04-11 Andy Hertzfeld <andy@eazel.com> + + created the post-1_0_11 branch by merging with head + + * Makefile.am: + * components/Makefile.am: + * components/rss-control/.cvsignore: + * components/rss-control/Makefile.am: + * components/rss-control/main.c: (rss_control_object_destroyed), + (rss_control_make_object), (main): + * components/rss-control/nautilus-rss-control.c: + (nautilus_rss_control_initialize_class), (get_bonobo_properties), + (set_bonobo_properties), (nautilus_rss_control_initialize), + (free_rss_data_item), (nautilus_rss_control_clear_items), + (nautilus_rss_control_destroy), (nautilus_rss_control_get_control), + (nautilus_rss_control_set_title), (rss_logo_callback), + (extract_items), (rss_read_done_callback), (load_rss_file), + (nautilus_rss_control_set_uri), (check_for_update), + (rss_control_pixbuf_composite), (draw_rss_logo_image), + (draw_rss_title), (draw_blue_line), (draw_rss_items), + (nautilus_rss_control_draw), (nautilus_rss_control_expose), + (nautilus_rss_control_set_prelight_index), + (nautilus_rss_control_motion_event), + (nautilus_rss_control_leave_event), + (nautilus_rss_control_size_request), + (nautilus_rss_control_button_press_event): + * components/rss-control/nautilus-rss-control.h: + * components/rss-control/nautilus-rss-control.oafinfo: + * components/throbber/nautilus-throbber.c: (get_bonobo_properties), + (set_bonobo_properties), (nautilus_throbber_initialize): + * components/vcard/.cvsignore: + * components/vcard/Makefile.am: + * components/vcard/main.c: (vcard_object_destroyed), + (vcard_make_object), (main): + * components/vcard/nautilus-vcard.c: + (nautilus_vcard_initialize_class), (get_bonobo_properties), + (set_bonobo_properties), (nautilus_vcard_initialize), + (nautilus_vcard_destroy), (nautilus_vcard_get_control), + (vcard_logo_callback), (vcard_read_done_callback), (load_vcard), + (nautilus_vcard_set_uri), (vcard_pixbuf_composite), + (draw_vcard_logo_image), (draw_vcard_name_and_title), + (draw_vcard_addresses), (nautilus_vcard_draw), + (nautilus_vcard_expose), (nautilus_vcard_motion_event), + (nautilus_vcard_size_request), (nautilus_vcard_leave_event), + (nautilus_vcard_button_press_event): + * components/vcard/nautilus-vcard.h: + * components/vcard/nautilus-vcard.oaf: + * components/vcard/vcard.c: (vcard_full_name), (vcard_title), + (vcard_logo), (vcard_postal), (vcard_streetaddress), + (vcard_city_state_zip), (vcard_company), (vcard_email), + (vcard_fax), (vcard_web), (vcard_address_list): + * components/vcard/vcard.h: + * configure.in: + * icons/Makefile.am: + * icons/emblem-note.png: + * libnautilus-extensions/Makefile.am: + * libnautilus-extensions/nautilus-annotation.c: (_byte_reverse), + (md5_init), (md5_update), (md5_final), (md5_transform), + (digest_file_close_callback), (digest_file_completed), + (digest_file_failed), (calculate_checksum_callback), + (read_file_open_callback), (calculate_file_digest), + (process_digest_requests), (queue_file_digest_request), + (get_file_from_digest), (get_annotation_path), + (look_up_local_annotation), (has_local_annotation), + (save_local_annotations), (add_annotations_to_file), + (remember_file), (forget_file), (got_annotations_callback), + (fetch_annotations_from_server), (get_annotation_from_server), + (got_file_digest), (annotation_is_stale), + (nautilus_annotation_get_annotation), + (nautilus_annotation_get_display_text), + (nautilus_annotation_get_annotation_for_display), + (nautilus_annotation_has_annotation), (http_post_simple), + (count_annotations), (nautilus_annotation_send_to_server), + (nautilus_annotation_add_annotation), + (nautilus_annotation_remove_annotation): + * libnautilus-extensions/nautilus-annotation.h: + * libnautilus-extensions/nautilus-canvas-note-item.c: + (nautilus_canvas_note_item_get_type), + (nautilus_canvas_note_item_class_init), + (nautilus_canvas_note_item_init), + (nautilus_canvas_note_item_destroy), (get_bounds), + (set_gc_foreground), (set_stipple), (set_outline_gc_width), + (update_item_bounding_box), + (nautilus_canvas_note_item_set_note_text), + (nautilus_canvas_note_item_set_arg), (get_color_arg), + (nautilus_canvas_note_item_get_arg), + (nautilus_canvas_note_item_realize), + (nautilus_canvas_note_item_unrealize), + (nautilus_canvas_note_item_translate), + (nautilus_canvas_note_item_bounds), (draw_item_aa_text), + (nautilus_canvas_note_item_render), + (nautilus_canvas_note_item_draw), + (nautilus_canvas_note_item_point), + (nautilus_canvas_note_item_update): + * libnautilus-extensions/nautilus-canvas-note-item.h: + * libnautilus-extensions/nautilus-file-utilities.c: + (nautilus_get_user_main_directory): + * libnautilus-extensions/nautilus-file.c: + (prepend_automatic_emblem_names): + * libnautilus-extensions/nautilus-file.h: + * libnautilus-extensions/nautilus-global-preferences.c: + (global_preferences_install_defaults), + (global_preferences_create_dialog): + * libnautilus-extensions/nautilus-global-preferences.h: + * libnautilus-extensions/nautilus-icon-canvas-item.c: + (nautilus_icon_canvas_item_initialize), + (nautilus_icon_canvas_item_destroy), + (nautilus_icon_canvas_item_get_icon_width), + (nautilus_icon_canvas_item_get_icon_height), + (nautilus_icon_canvas_item_set_arg), (do_control_destroy), + (nautilus_icon_canvas_item_get_arg), + (nautilus_icon_canvas_item_get_image), (recompute_bounding_box), + (nautilus_icon_canvas_item_update_bounds), + (draw_or_measure_label_text), (emblem_layout_next), + (nautilus_icon_canvas_item_draw), (draw_or_measure_label_text_aa), + (nautilus_icon_canvas_item_render), (create_annotation), + (remove_annotation), (create_annotation_timeout_callback), + (nautilus_icon_canvas_item_set_note_state), + (nautilus_icon_canvas_item_event), + (nautilus_icon_canvas_item_hit_test_full), + (nautilus_icon_canvas_item_point), + (nautilus_icon_canvas_item_bounds), + (nautilus_icon_canvas_item_get_icon_rectangle), + (get_emblem_rectangle), (get_icon_canvas_rectangle), + (nautilus_icon_canvas_item_hit_test_rectangle), + (nautilus_icon_canvas_item_set_smooth_font), + (nautilus_icon_canvas_item_get_control), + (nautilus_icon_canvas_item_set_control): + * libnautilus-extensions/nautilus-icon-canvas-item.h: + * libnautilus-extensions/nautilus-icon-container.c: (destroy), + (hit_test_item), (nautilus_icon_container_did_not_drag), + (key_press_event), (nautilus_icon_container_initialize_class), + (nautilus_icon_container_initialize), (handle_icon_button_press), + (activate_selected_items), (nautilus_icon_container_update_icon), + (nautilus_icon_container_annotation_changed), + (nautilus_icon_container_get_note_text): + * libnautilus-extensions/nautilus-icon-container.h: + * libnautilus-extensions/nautilus-icon-dnd.c: + (nautilus_icon_dnd_begin_drag): + * libnautilus-extensions/nautilus-icon-private.h: + * libnautilus-extensions/nautilus-link.c: + (nautilus_link_local_get_component_info): + * libnautilus-extensions/nautilus-link.h: + * libnautilus-extensions/nautilus-metadata.h: + * nautilus-clean.sh: + * src/file-manager/Makefile.am: + * src/file-manager/fm-annotation-window.c: + (fm_annotation_window_initialize_class), + (fm_annotation_window_initialize), (real_destroy), + (get_pixbuf_for_annotation_window), + (update_annotation_window_icon), (create_image_widget_for_file), + (update_annotation_window_title), (set_access_mode), + (add_access_menu_item), (create_options_table), + (annotation_clicked_callback), (create_annotation_window), + (fm_annotation_window_present): + * src/file-manager/fm-annotation-window.h: + * src/file-manager/fm-icon-view.c: (annotate_callback), + (fm_icon_view_merge_menus), (fm_icon_view_update_menus), + (get_keyword_by_index), (icon_container_activate_callback), + (get_icon_control_callback), (get_icon_text_callback), + (get_icon_annotation_callback), (create_icon_container): + * src/file-manager/nautilus-icon-view-ui.xml: + * src/nautilus-property-browser.c: + (nautilus_property_browser_update_contents): + 2001-04-11 Ramiro Estrugo <ramiro@eazel.com> * nautilus.spec.in: @@ -876,6 +1041,289 @@ a nicely formatted text string, for everything else just ellipsize the result. +2001-04-11 Andy Hertzfeld <andy@eazel.com> + + * libnautilus-extensions/nautilus-canvas-note-item.c: + (nautilus_canvas_note_item_set_note_text): + improved sizing of the note item in the aa mode. + + * libnautilus-extensions/nautilus-icon-container.c: + (hit_test_item): + fixed bug by mapping from window to world coordinates + +2001-04-10 Andy Hertzfeld <andy@eazel.com> + + added a way for custom actions to take place when the user clicks + on an emblem, and used that to bring up the annotation window + when the "note" emblem is clicked on. + + * libnautilus-extensions/nautilus-icon-canvas-item.c,h: + (nautilus_icon_canvas_item_event), + (nautilus_icon_canvas_item_hit_test_full), + (nautilus_icon_canvas_item_point), + (nautilus_icon_canvas_item_hit_test_rectangle): + renamed the "hit_test" routine to nautilus_icon_canvas_hit_test_full, + and made it public. + + * libnautilus-extensions/nautilus-icon-container.c: + (hit_test_item), (nautilus_icon_container_did_not_drag), + (key_press_event), (nautilus_icon_container_initialize_class), + (handle_icon_button_press), (activate_selected_items): + added an emblem index parameter to the icon canvas's activate + signal, and set it up properly by calling the item hit test + routine to determine the emblem index. + + * src/file-manager/fm-icon-view.c: (get_keyword_by_index), + (icon_container_activate_callback), (get_icon_annotation_callback): + changed the activate callback to bring up the annotation window + if the annotation emblem is clicked. + +2001-04-10 Andy Hertzfeld <andy@eazel.com> + + * libnautilus-extensions/nautilus-canvas-note-item.c: + (nautilus_canvas_note_item_set_note_text), + (nautilus_canvas_note_item_update): + tweaked note sizing + + * libnautilus-extensions/nautilus-icon-canvas-item.c: + (create_annotation), (get_emblem_rectangle): + made the annotation be positioned properly by using world coordinates + instead of canvas coordinates + +2001-04-09 Andy Hertzfeld <andy@eazel.com> + + removed some unneeded generality from the note item + + * libnautilus-extensions/nautilus-canvas-note-item.c: + (nautilus_canvas_note_item_set_arg), + (nautilus_canvas_note_item_draw), + (nautilus_canvas_note_item_point): + * libnautilus-extensions/nautilus-canvas-note-item.h: + +2001-04-09 Andy Hertzfeld <andy@eazel.com> + + implemented arrow pointer for annotations, to make it more + clear what they're annotating. + + * libnautilus-extensions/nautilus-annotation.c: + (nautilus_annotation_send_to_server): + removed debugging message + + * libnautilus-extensions/nautilus-canvas-note-item.c: + (nautilus_canvas_note_item_set_note_text), (draw_item_aa_text), + (nautilus_canvas_note_item_update): + draw the arrow pointer + + * libnautilus-extensions/nautilus-icon-canvas-item.c: + (create_annotation), (get_emblem_rectangle): + position the annotation so the arrow points at the right place. + +2001-04-09 Andy Hertzfeld <andy@eazel.com> + + * libnautilus-extensions/Makefile.am: + linked with ammonite so we can get the user name + * libnautilus-extensions/nautilus-annotation.c: + (count_annotations), (nautilus_annotation_send_to_server), + (nautilus_annotation_add_annotation): + made it maintain the annotation count properly when a new annotation + is added; also, use the real username fetched from ammonite. + +2001-04-08 Andy Hertzfeld <andy@eazel.com> + + * libnautilus-extensions/nautilus-annotation.c: + (annotation_is_stale), (nautilus_annotation_get_annotation), + (nautilus_annotation_has_annotation), + (nautilus_annotation_send_to_server), + (nautilus_annotation_add_annotation): + made annotation upload work; added "date" property to distinguish + multiple annotations/file; refetch annotations periodically + +2001-04-06 Andy Hertzfeld <andy@eazel.com> + + * libnautilus-extensions/nautilus-annotation.c: + (nautilus_annotation_has_annotation), (http_post_simple), + (nautilus_annotation_send_to_server), + (nautilus_annotation_add_annotation): + implemented client side of annotation upload; still need to do + server side before it can be tested. + +2001-04-06 Andy Hertzfeld <andy@eazel.com> + + started implementation of optionally sending annotations to the + server. Also, put up error when the user tries to annotate + a directory + + * libnautilus-extensions/nautilus-annotation.c: + (nautilus_annotation_send_to_server), + (nautilus_annotation_add_annotation): + + * src/file-manager/fm-annotation-window.c: + (fm_annotation_window_initialize), (real_destroy), + (set_access_mode), (add_access_menu_item), (create_options_table), + (annotation_clicked_callback), (create_annotation_window), + (fm_annotation_window_present): + +2001-04-05 Andy Hertzfeld <andy@eazel.com> + + merged with HEAD to create the post-1_0_10 branch + + * Makefile.am: + * components/Makefile.am: + * components/rss-control/.cvsignore: + * components/rss-control/Makefile.am: + * components/rss-control/main.c: (rss_control_object_destroyed), + (rss_control_make_object), (main): + * components/rss-control/nautilus-rss-control.c: + (nautilus_rss_control_initialize_class), (get_bonobo_properties), + (set_bonobo_properties), (nautilus_rss_control_initialize), + (free_rss_data_item), (nautilus_rss_control_clear_items), + (nautilus_rss_control_destroy), (nautilus_rss_control_get_control), + (nautilus_rss_control_set_title), (rss_logo_callback), + (extract_items), (rss_read_done_callback), (load_rss_file), + (nautilus_rss_control_set_uri), (check_for_update), + (rss_control_pixbuf_composite), (draw_rss_logo_image), + (draw_rss_title), (draw_blue_line), (draw_rss_items), + (nautilus_rss_control_draw), (nautilus_rss_control_expose), + (nautilus_rss_control_set_prelight_index), + (nautilus_rss_control_motion_event), + (nautilus_rss_control_leave_event), + (nautilus_rss_control_size_request), + (nautilus_rss_control_button_press_event): + * components/rss-control/nautilus-rss-control.h: + * components/rss-control/nautilus-rss-control.oafinfo: + * components/throbber/nautilus-throbber.c: (get_bonobo_properties), + (set_bonobo_properties), (nautilus_throbber_initialize): + * components/vcard/.cvsignore: + * components/vcard/Makefile.am: + * components/vcard/main.c: (vcard_object_destroyed), + (vcard_make_object), (main): + * components/vcard/nautilus-vcard.c: + (nautilus_vcard_initialize_class), (get_bonobo_properties), + (set_bonobo_properties), (nautilus_vcard_initialize), + (nautilus_vcard_destroy), (nautilus_vcard_get_control), + (vcard_logo_callback), (vcard_read_done_callback), (load_vcard), + (nautilus_vcard_set_uri), (vcard_pixbuf_composite), + (draw_vcard_logo_image), (draw_vcard_name_and_title), + (draw_vcard_addresses), (nautilus_vcard_draw), + (nautilus_vcard_expose), (nautilus_vcard_motion_event), + (nautilus_vcard_size_request), (nautilus_vcard_leave_event), + (nautilus_vcard_button_press_event): + * components/vcard/nautilus-vcard.h: + * components/vcard/nautilus-vcard.oaf: + * components/vcard/vcard.c: (vcard_full_name), (vcard_title), + (vcard_logo), (vcard_postal), (vcard_streetaddress), + (vcard_city_state_zip), (vcard_company), (vcard_email), + (vcard_fax), (vcard_web), (vcard_address_list): + * components/vcard/vcard.h: + * configure.in: + * icons/Makefile.am: + * icons/emblem-note.png: + * libnautilus-extensions/Makefile.am: + * libnautilus-extensions/nautilus-annotation.c: (_byte_reverse), + (md5_init), (md5_update), (md5_final), (md5_transform), + (digest_file_close_callback), (digest_file_completed), + (digest_file_failed), (calculate_checksum_callback), + (read_file_open_callback), (calculate_file_digest), + (process_digest_requests), (queue_file_digest_request), + (get_file_from_digest), (get_annotation_path), + (look_up_local_annotation), (has_local_annotation), + (save_local_annotations), (add_annotations_to_file), + (remember_file), (forget_file), (got_annotations_callback), + (fetch_annotations_from_server), (get_annotation_from_server), + (got_file_digest), (nautilus_annotation_get_annotation), + (nautilus_annotation_get_display_text), + (nautilus_annotation_get_annotation_for_display), + (nautilus_annotation_has_annotation), + (nautilus_annotation_add_annotation), + (nautilus_annotation_remove_annotation): + * libnautilus-extensions/nautilus-annotation.h: + * libnautilus-extensions/nautilus-canvas-note-item.c: + (nautilus_canvas_note_item_get_type), + (nautilus_canvas_note_item_class_init), + (nautilus_canvas_note_item_init), + (nautilus_canvas_note_item_destroy), (get_bounds), + (set_gc_foreground), (set_stipple), (set_outline_gc_width), + (nautilus_canvas_note_item_set_fill), + (nautilus_canvas_note_item_set_outline), + (update_item_bounding_box), + (nautilus_canvas_note_item_set_note_text), + (nautilus_canvas_note_item_set_arg), (get_color_arg), + (nautilus_canvas_note_item_get_arg), + (nautilus_canvas_note_item_realize), + (nautilus_canvas_note_item_unrealize), + (nautilus_canvas_note_item_translate), + (nautilus_canvas_note_item_bounds), (draw_item_aa_text), + (nautilus_canvas_note_item_render), + (nautilus_canvas_note_item_draw), + (nautilus_canvas_note_item_point), + (nautilus_canvas_note_item_update): + * libnautilus-extensions/nautilus-canvas-note-item.h: + * libnautilus-extensions/nautilus-file-utilities.c: + (nautilus_get_user_main_directory): + * libnautilus-extensions/nautilus-file.c: + (prepend_automatic_emblem_names): + * libnautilus-extensions/nautilus-file.h: + * libnautilus-extensions/nautilus-global-preferences.c: + (global_preferences_install_defaults), + (global_preferences_create_dialog): + * libnautilus-extensions/nautilus-global-preferences.h: + * libnautilus-extensions/nautilus-icon-canvas-item.c: + (nautilus_icon_canvas_item_initialize), + (nautilus_icon_canvas_item_destroy), + (nautilus_icon_canvas_item_get_icon_width), + (nautilus_icon_canvas_item_get_icon_height), + (nautilus_icon_canvas_item_set_arg), (do_control_destroy), + (nautilus_icon_canvas_item_get_arg), + (nautilus_icon_canvas_item_get_image), (recompute_bounding_box), + (nautilus_icon_canvas_item_update_bounds), + (draw_or_measure_label_text), (emblem_layout_next), + (nautilus_icon_canvas_item_draw), (draw_or_measure_label_text_aa), + (nautilus_icon_canvas_item_render), (create_annotation), + (remove_annotation), (create_annotation_timeout_callback), + (nautilus_icon_canvas_item_set_note_state), + (nautilus_icon_canvas_item_event), (hit_test), + (nautilus_icon_canvas_item_point), + (nautilus_icon_canvas_item_bounds), + (nautilus_icon_canvas_item_get_icon_rectangle), + (get_icon_canvas_rectangle), + (nautilus_icon_canvas_item_hit_test_rectangle), + (nautilus_icon_canvas_item_set_smooth_font), + (nautilus_icon_canvas_item_get_control), + (nautilus_icon_canvas_item_set_control): + * libnautilus-extensions/nautilus-icon-canvas-item.h: + * libnautilus-extensions/nautilus-icon-container.c: (destroy), + (nautilus_icon_container_initialize_class), + (nautilus_icon_container_initialize), + (nautilus_icon_container_update_icon), + (nautilus_icon_container_annotation_changed), + (nautilus_icon_container_get_note_text): + * libnautilus-extensions/nautilus-icon-container.h: + * libnautilus-extensions/nautilus-icon-dnd.c: + (nautilus_icon_dnd_begin_drag): + * libnautilus-extensions/nautilus-icon-private.h: + * libnautilus-extensions/nautilus-link.c: + (nautilus_link_local_get_component_info): + * libnautilus-extensions/nautilus-link.h: + * libnautilus-extensions/nautilus-metadata.h: + * nautilus-clean.sh: + * src/file-manager/Makefile.am: + * src/file-manager/fm-annotation-window.c: + (fm_annotation_window_initialize_class), + (fm_annotation_window_initialize), (real_destroy), + (get_pixbuf_for_annotation_window), + (update_annotation_window_icon), (create_image_widget_for_file), + (update_annotation_window_title), (create_options_table), + (annotation_clicked_callback), (create_annotation_window), + (fm_annotation_window_present): + * src/file-manager/fm-annotation-window.h: + * src/file-manager/fm-icon-view.c: (annotate_callback), + (fm_icon_view_merge_menus), (fm_icon_view_update_menus), + (get_icon_control_callback), (get_icon_text_callback), + (get_icon_annotation_callback), (create_icon_container): + * src/file-manager/nautilus-icon-view-ui.xml: + * src/nautilus-property-browser.c: + (nautilus_property_browser_update_contents): + 2001-04-05 John Sullivan <sullivan@eazel.com> Fixed bug 7986 ("Paste Files" disabled in trash:) diff --git a/Makefile.am b/Makefile.am index f859a775f..062862726 100644 --- a/Makefile.am +++ b/Makefile.am @@ -12,7 +12,6 @@ endif SUBDIRS = \ data \ icons \ - intl \ cut-n-paste-code \ libnautilus \ libnautilus-adapter \ diff --git a/components/Makefile.am b/components/Makefile.am index 46761130d..559ddec69 100644 --- a/components/Makefile.am +++ b/components/Makefile.am @@ -13,6 +13,7 @@ SUBDIRS = \ loser \ music \ notes \ + rss-control \ sample \ text \ throbber \ diff --git a/components/rss-control/.cvsignore b/components/rss-control/.cvsignore new file mode 100644 index 000000000..9eeb873c6 --- /dev/null +++ b/components/rss-control/.cvsignore @@ -0,0 +1,5 @@ +.deps +.libs +Makefile +Makefile.in +nautilus-rss-control diff --git a/components/rss-control/Makefile.am b/components/rss-control/Makefile.am new file mode 100644 index 000000000..db0531dd4 --- /dev/null +++ b/components/rss-control/Makefile.am @@ -0,0 +1,49 @@ +NULL = + +SUBDIRS = + +INCLUDES = \ + -DPREFIX=\"$(prefix)\" \ + -DG_LOG_DOMAIN=\"Nautilus-RSS-Control\" \ + -DDATADIR=\""$(datadir)"\" \ + -I$(top_srcdir) \ + -I$(top_builddir) \ + -I$(top_builddir)/libnautilus \ + -DGNOMELOCALEDIR=\""$(datadir)/locale"\" \ + $(EEL_INCLUDEDIR) \ + $(LIBRSVG_INCLUDEDIR) \ + $(GNOMEUI_CFLAGS) \ + $(GCONF_CFLAGS) \ + $(OAF_CFLAGS) \ + $(BONOBO_CFLAGS) \ + $(VFS_CFLAGS) + +oafdir = $(datadir)/oaf +oaf_DATA = \ + nautilus-rss-control.oafinfo + + +bin_PROGRAMS = \ + nautilus-rss-control + +nautilus_rss_control_SOURCES = \ + nautilus-rss-control.c \ + nautilus-rss-control.h \ + main.c + +nautilus_rss_control_LDADD = \ + $(top_builddir)/libnautilus/libnautilus.la \ + $(top_builddir)/libnautilus-extensions/libnautilus-extensions.la \ + $(EEL_LIBS) \ + $(LIBRSVG_LIBS) \ + $(BONOBO_LIBS) \ + $(GNOMEUI_LIBS) \ + $(GCONF_LIBS) \ + $(VFS_LIBS) \ + $(GNORBA_LIBS) + +OBJECT_DIRECTORY_LIBS = $(GNOME_LIBS) $(OAF_LIBS) + +EXTRA_DIST = \ + $(oaf_DATA) \ + $(NULL) diff --git a/components/rss-control/main.c b/components/rss-control/main.c new file mode 100644 index 000000000..ada73e46b --- /dev/null +++ b/components/rss-control/main.c @@ -0,0 +1,120 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* + * Copyright (C) 2000 Eazel, Inc + * + * 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: Andy Hertzfeld + */ + +/* main.c - main function and object activation function for the rss control component. */ + +#include <config.h> +#include "nautilus-rss-control.h" + +#include <bonobo.h> +#include <gnome.h> +#include <libgnomevfs/gnome-vfs.h> +#include <eel/eel-debug.h> +#include <liboaf/liboaf.h> + +static int object_count = 0; + +static void +rss_control_object_destroyed(GtkObject *obj) +{ + object_count--; + if (object_count <= 0) { + gtk_main_quit (); + } +} + +static BonoboObject * +rss_control_make_object (BonoboGenericFactory *factory, + const char *iid, + void *closure) +{ + NautilusRSSControl *rss_control; + BonoboObject *bonobo_control; + + if (strcmp (iid, "OAFIID:nautilus_rss_control:1230")) { + return NULL; + } + + rss_control = NAUTILUS_RSS_CONTROL (gtk_object_new (NAUTILUS_TYPE_RSS_CONTROL, NULL)); + + object_count++; + + bonobo_control = nautilus_rss_control_get_control (rss_control); + + gtk_signal_connect (GTK_OBJECT (bonobo_control), "destroy", rss_control_object_destroyed, NULL); + return bonobo_control; +} + +int +main (int argc, char *argv[]) +{ + BonoboGenericFactory *factory; + CORBA_ORB orb; + char *registration_id; + + /* Make criticals and warnings stop in the debugger if + * NAUTILUS_DEBUG is set. Unfortunately, this has to be done + * explicitly for each domain. + */ + if (g_getenv ("NAUTILUS_DEBUG") != NULL) { + eel_make_warnings_and_criticals_stop_in_debugger + (G_LOG_DOMAIN, g_log_domain_glib, + "Bonobo", + "Gdk", + "GnomeUI", + "GnomeVFS", + "GnomeVFS-CORBA", + "GnomeVFS-pthread", + "Gtk", + "Gdk-Pixbuf", + "Nautilus", + "Nautilus-Authenticate", + "Nautilus-Tree", + "ORBit", + NULL); + } + + gnome_init_with_popt_table("nautilus-rss-control", VERSION, + argc, argv, + oaf_popt_options, 0, NULL); + + orb = oaf_init (argc, argv); + + bonobo_init (orb, CORBA_OBJECT_NIL, CORBA_OBJECT_NIL); + + /* initialize gnome-vfs, etc */ + g_thread_init (NULL); + gnome_vfs_init (); + + registration_id = oaf_make_registration_id ("OAFIID:nautilus_rss_control_factory:1230", getenv ("DISPLAY")); + factory = bonobo_generic_factory_new_multi (registration_id, + rss_control_make_object, + NULL); + g_free (registration_id); + + + do { + bonobo_main (); + } while (object_count > 0); + + return 0; +} diff --git a/components/rss-control/nautilus-rss-control.c b/components/rss-control/nautilus-rss-control.c new file mode 100644 index 000000000..4ec56cffa --- /dev/null +++ b/components/rss-control/nautilus-rss-control.c @@ -0,0 +1,872 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ + +/* + * Copyright (C) 2000 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 <andy@eazel.com> + * + */ + +/* this is the implementation of the rss control, which fetches an rss file through a uri, and + * displays it in the widget. + */ + +#include <config.h> +#include <time.h> +#include <gnome.h> +#include <liboaf/liboaf.h> + +#include <bonobo.h> + +#include "nautilus-rss-control.h" +#include <ghttp.h> + +#include <gnome-xml/parser.h> +#include <gnome-xml/xmlmemory.h> + +#include <libgnomevfs/gnome-vfs.h> + +#include <libnautilus/nautilus-view.h> + +#include <libnautilus-extensions/nautilus-file-utilities.h> +#include <eel/eel-gdk-extensions.h> +#include <eel/eel-gdk-pixbuf-extensions.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-gtk-macros.h> +#include <eel/eel-scalable-font.h> +#include <eel/eel-string.h> +#include <eel/eel-xml-extensions.h> +#include <libnautilus-extensions/nautilus-font-factory.h> +#include <eel/eel-vfs-extensions.h> + +/* private instance variables */ +struct _NautilusRSSControlDetails { + char* rss_uri; + BonoboObject *control; + EelScalableFont *font; + + EelReadFileHandle *load_file_handle; + EelPixbufLoadHandle *load_image_handle; + + int items_v_offset; + int prelight_index; + + char* title; + char* main_uri; + + guint timer_task; + time_t last_update; + uint update_interval; + + GdkPixbuf *logo; + GdkPixbuf *bullet; + GList *items; +}; + +/* per item structure for rss items */ +typedef struct { + char *item_title; + char *item_url; + +} RSSItemData; + +#define RSS_ITEM_HEIGHT 15 +#define MINIMUM_DRAW_SIZE 8 + +static void nautilus_rss_control_initialize_class (NautilusRSSControlClass *klass); +static void nautilus_rss_control_initialize (NautilusRSSControl *view); +static void nautilus_rss_control_destroy (GtkObject *object); + +static void nautilus_rss_control_draw (GtkWidget *widget, GdkRectangle *box); +static int nautilus_rss_control_expose (GtkWidget *widget, GdkEventExpose *event); +static gboolean nautilus_rss_control_button_press_event (GtkWidget *widget, GdkEventButton *event); +static gboolean nautilus_rss_control_motion_event (GtkWidget *widget, GdkEventMotion *event); +static gboolean nautilus_rss_control_leave_event (GtkWidget *widget, GdkEventCrossing *event); +static void nautilus_rss_control_size_request (GtkWidget *widget, GtkRequisition *request); + +static void nautilus_rss_control_set_uri (NautilusRSSControl *rss_control, const char *uri); +static int check_for_update (gpointer callback_data); + + +EEL_DEFINE_CLASS_BOILERPLATE (NautilusRSSControl, + nautilus_rss_control, + GTK_TYPE_EVENT_BOX) + + +static void +nautilus_rss_control_initialize_class (NautilusRSSControlClass *klass) +{ + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + + object_class = GTK_OBJECT_CLASS (klass); + widget_class = GTK_WIDGET_CLASS (klass); + + object_class->destroy = nautilus_rss_control_destroy; + + widget_class->draw = nautilus_rss_control_draw; + widget_class->expose_event = nautilus_rss_control_expose; + widget_class->button_press_event = nautilus_rss_control_button_press_event; + widget_class->motion_notify_event = nautilus_rss_control_motion_event; + widget_class->leave_notify_event = nautilus_rss_control_leave_event; + widget_class->size_request = nautilus_rss_control_size_request; +} + +/* routines to handle setting and getting the configuration properties of the Bonobo control */ + +enum { + CONFIGURATION +} MyArgs; + + +static void +get_bonobo_properties (BonoboPropertyBag *bag, + BonoboArg *arg, + guint arg_id, + CORBA_Environment *ev, + gpointer user_data) +{ + NautilusRSSControl *rss_control = NAUTILUS_RSS_CONTROL (user_data); + + switch (arg_id) { + + case CONFIGURATION: + { + BONOBO_ARG_SET_STRING (arg, rss_control->details->rss_uri); + break; + } + + default: + g_warning ("Unhandled arg %d", arg_id); + break; + } +} + +static void +set_bonobo_properties (BonoboPropertyBag *bag, + const BonoboArg *arg, + guint arg_id, + CORBA_Environment *ev, + gpointer user_data) +{ + NautilusRSSControl *rss_control = NAUTILUS_RSS_CONTROL (user_data); + + switch (arg_id) { + + case CONFIGURATION: + { + char *uri; + + uri = BONOBO_ARG_GET_STRING (arg); + nautilus_rss_control_set_uri (rss_control, uri); + + break; + } + + default: + g_warning ("Unhandled arg %d", arg_id); + break; + } +} + +/* initialize ourselves by connecting to the location change signal and allocating our subviews */ +static void +nautilus_rss_control_initialize (NautilusRSSControl *rss_control) +{ + char *bullet_path; + BonoboPropertyBag *property_bag; + + rss_control->details = g_new0 (NautilusRSSControlDetails, 1); + + /* set up the font */ + rss_control->details->font = eel_scalable_font_get_default_font (); + rss_control->details->prelight_index = -1; + + /* set up the update variables */ + rss_control->details->last_update = 0; + rss_control->details->update_interval = 2 * 60; /* default to once every two minutes */ + + rss_control->details->timer_task + = gtk_timeout_add (4000, check_for_update, rss_control); + + /* load the bullet used to display the items */ + bullet_path = nautilus_pixmap_file ("bullet.png"); + rss_control->details->bullet = gdk_pixbuf_new_from_file (bullet_path); + g_free (bullet_path); + + /* receive mouse motion events */ + gtk_widget_add_events (GTK_WIDGET (rss_control), GDK_POINTER_MOTION_MASK); + + /* make the bonobo control */ + rss_control->details->control = (BonoboObject*) bonobo_control_new (GTK_WIDGET (rss_control)); + + /* attach a property bag with the configure property */ + property_bag = bonobo_property_bag_new (get_bonobo_properties, set_bonobo_properties, rss_control); + bonobo_control_set_properties (BONOBO_CONTROL(rss_control->details->control),property_bag); + bonobo_object_unref (BONOBO_OBJECT (property_bag)); + + bonobo_property_bag_add (property_bag, "configuration", CONFIGURATION, BONOBO_ARG_STRING, NULL, + "RSS Configuration", BONOBO_PROPERTY_WRITEABLE); + + /* show the view itself */ + gtk_widget_show (GTK_WIDGET (rss_control)); +} + +static void +free_rss_data_item (RSSItemData *item) +{ + g_free (item->item_title); + g_free (item->item_url); + g_free (item); +} + +static void +nautilus_rss_control_clear_items (NautilusRSSControl *rss_control) +{ + if (rss_control->details->items != NULL) { + eel_g_list_free_deep_custom (rss_control->details->items, (GFunc) free_rss_data_item, NULL); + rss_control->details->items = NULL; + } +} + +static void +nautilus_rss_control_destroy (GtkObject *object) +{ + NautilusRSSControl *rss_control; + + rss_control = NAUTILUS_RSS_CONTROL (object); + g_free (rss_control->details->rss_uri); + g_free (rss_control->details->title); + g_free (rss_control->details->main_uri); + + if (rss_control->details->load_file_handle != NULL) { + eel_read_file_cancel (rss_control->details->load_file_handle); + } + + if (rss_control->details->load_image_handle != NULL) { + eel_cancel_gdk_pixbuf_load (rss_control->details->load_image_handle); + } + + if (rss_control->details->logo != NULL) { + gdk_pixbuf_unref (rss_control->details->logo); + } + + if (rss_control->details->bullet != NULL) { + gdk_pixbuf_unref (rss_control->details->bullet); + } + + if (rss_control->details->items != NULL) { + nautilus_rss_control_clear_items (rss_control); + } + + if (rss_control->details->font) { + gtk_object_unref (GTK_OBJECT (rss_control->details->font)); + } + + if (rss_control->details->timer_task != 0) { + gtk_timeout_remove (rss_control->details->timer_task); + rss_control->details->timer_task = 0; + } + + g_free (rss_control->details); + + EEL_CALL_PARENT (GTK_OBJECT_CLASS, destroy, (object)); +} + + +/* get associated Bonobo control */ +BonoboObject * +nautilus_rss_control_get_control (NautilusRSSControl *rss_control) +{ + return rss_control->details->control; +} + +static void +nautilus_rss_control_set_title (NautilusRSSControl *rss_control, const char *title) +{ + if (eel_strcmp (rss_control->details->title, title) == 0) { + return; + } + + if (rss_control->details->title) { + g_free (rss_control->details->title); + } + if (title != NULL) { + rss_control->details->title = g_strdup (title); + } else { + rss_control->details->title = NULL; + + } + gtk_widget_queue_draw (GTK_WIDGET (rss_control)); +} + + +static void +rss_logo_callback (GnomeVFSResult error, GdkPixbuf *pixbuf, gpointer callback_data) +{ + NautilusRSSControl *rss_control; + + rss_control = NAUTILUS_RSS_CONTROL (callback_data); + rss_control->details->load_image_handle = NULL; + + if (rss_control->details->logo) { + gdk_pixbuf_unref (rss_control->details->logo); + } + + if (pixbuf != NULL) { + gdk_pixbuf_ref (pixbuf); + rss_control->details->logo = pixbuf; + gtk_widget_queue_draw (GTK_WIDGET (rss_control)); + } +} + +/* utility routine to extract items from a node, returning the count of items found */ +static int +extract_items (NautilusRSSControl *rss_control, xmlNodePtr container_node) +{ + RSSItemData *item_parameters; + xmlNodePtr current_node, title_node, temp_node; + int item_count; + char *title, *temp_str; + + current_node = container_node->childs; + item_count = 0; + while (current_node != NULL) { + if (eel_strcmp (current_node->name, "item") == 0) { + title_node = eel_xml_get_child_by_name (current_node, "title"); + if (title_node) { + item_parameters = (RSSItemData*) g_new0 (RSSItemData, 1); + + title = xmlNodeGetContent (title_node); + item_parameters->item_title = g_strdup (title); + xmlFree (title); + temp_node = eel_xml_get_child_by_name (current_node, "link"); + + if (temp_node) { + temp_str = xmlNodeGetContent (temp_node); + item_parameters->item_url = g_strdup (temp_str); + xmlFree (temp_str); + } + + rss_control->details->items = g_list_append (rss_control->details->items, item_parameters); + item_count += 1; + } + } + current_node = current_node->next; + } + return item_count; +} + +/* completion routine invoked when we've loaded the rss file uri. Parse the xml document, and + * then extract the various elements that we require */ + +static void +rss_read_done_callback (GnomeVFSResult result, + GnomeVFSFileSize file_size, + char *file_contents, + gpointer callback_data) +{ + xmlDocPtr rss_document; + xmlNodePtr image_node, channel_node; + xmlNodePtr current_node, temp_node, uri_node; + char *image_uri, *title, *temp_str; + int item_count; + NautilusRSSControl *rss_control; + + char *buffer; + + rss_control = NAUTILUS_RSS_CONTROL (callback_data); + rss_control->details->load_file_handle = NULL; + + /* make sure the read was successful */ + if (result != GNOME_VFS_OK) { + g_assert (file_contents == NULL); + return; + } + + /* flag the update time */ + time (&rss_control->details->last_update); + + /* Parse the rss file with gnome-xml. The gnome-xml parser requires a zero-terminated array. */ + buffer = g_realloc (file_contents, file_size + 1); + buffer[file_size] = '\0'; + rss_document = xmlParseMemory (buffer, file_size); + g_free (buffer); + + /* make sure there wasn't in error parsing the document */ + if (rss_document == NULL) { + return; + } + + /* extract the title and set it */ + channel_node = eel_xml_get_child_by_name (xmlDocGetRootElement (rss_document), "channel"); + if (channel_node != NULL) { + temp_node = eel_xml_get_child_by_name (channel_node, "title"); + if (temp_node != NULL) { + title = xmlNodeGetContent (temp_node); + if (title != NULL) { + nautilus_rss_control_set_title (rss_control, title); + xmlFree (title); + } + } + + temp_node = eel_xml_get_child_by_name (channel_node, "link"); + if (temp_node != NULL) { + temp_str = xmlNodeGetContent (temp_node); + if (temp_str != NULL) { + g_free (rss_control->details->main_uri); + rss_control->details->main_uri = g_strdup (temp_str); + xmlFree (temp_str); + } + } + + } + + /* extract the image uri and, if found, load it asynchronously */ + image_node = eel_xml_get_child_by_name (xmlDocGetRootElement (rss_document), "image"); + + /* if we can't find it at the top level, look inside the channel */ + if (image_node == NULL && channel_node != NULL) { + image_node = eel_xml_get_child_by_name (channel_node, "image"); + } + + if (image_node != NULL) { + uri_node = eel_xml_get_child_by_name (image_node, "url"); + if (uri_node != NULL) { + image_uri = xmlNodeGetContent (uri_node); + if (image_uri != NULL) { + rss_control->details->load_image_handle = eel_gdk_pixbuf_load_async (image_uri, rss_logo_callback, rss_control); + xmlFree (image_uri); + } + } + } + + /* extract the items in a loop */ + nautilus_rss_control_clear_items (rss_control); + current_node = rss_document->root; + item_count = extract_items (rss_control, current_node); + + /* if we couldn't find any items at the main level, look inside the channel node */ + if (item_count == 0 && channel_node != NULL) { + item_count = extract_items (rss_control, channel_node); + } + + /* we're done, so free everything up */ + xmlFreeDoc (rss_document); + + /* schedule a redraw to reflect the new contents */ + gtk_widget_queue_draw (GTK_WIDGET (rss_control)); +} + +/* load the rss file asynchronously */ +static void +load_rss_file (NautilusRSSControl *rss_control) +{ + char *title; + /* load the uri asynchrounously, calling a completion routine when completed */ + rss_control->details->load_file_handle = eel_read_entire_file_async (rss_control->details->rss_uri, rss_read_done_callback, rss_control); + + /* put up a title that's displayed while we wait */ + title = g_strdup_printf ("Loading %s", rss_control->details->rss_uri); + nautilus_rss_control_set_title (rss_control, title); + g_free (title); +} + +/* set the uri and load it */ +static void +nautilus_rss_control_set_uri (NautilusRSSControl *rss_control, const char *uri) +{ + + if (eel_strcmp (rss_control->details->rss_uri, uri) == 0) { + return; + } + + if (rss_control->details->rss_uri != NULL) { + g_free (rss_control->details->rss_uri); + rss_control->details->rss_uri = NULL; + } + + if (uri != NULL) { + rss_control->details->rss_uri = g_strdup (uri); + load_rss_file (rss_control); + } +} + +/* handle periodically updating the items if necessary */ +static int +check_for_update (gpointer callback_data) +{ + NautilusRSSControl *rss_control; + guint current_time, next_time; + + rss_control = NAUTILUS_RSS_CONTROL (callback_data); + + current_time = time (NULL); + next_time = rss_control->details->last_update + rss_control->details->update_interval; + + if (current_time > next_time) { + rss_control->details->load_file_handle = eel_read_entire_file_async (rss_control->details->rss_uri, rss_read_done_callback, rss_control); + rss_control->details->last_update = current_time; + } + return TRUE; +} + +/* convenience routine to composite an image with the proper clipping */ +static void +rss_control_pixbuf_composite (GdkPixbuf *source, GdkPixbuf *destination, int x_offset, int y_offset, int alpha) +{ + int source_width, source_height, dest_width, dest_height; + double float_x_offset, float_y_offset; + + source_width = gdk_pixbuf_get_width (source); + source_height = gdk_pixbuf_get_height (source); + dest_width = gdk_pixbuf_get_width (destination); + dest_height = gdk_pixbuf_get_height (destination); + + float_x_offset = x_offset; + float_y_offset = y_offset; + + /* clip to the destination size */ + if ((x_offset + source_width) > dest_width) { + source_width = dest_width - x_offset; + } + if ((y_offset + source_height) > dest_height) { + source_height = dest_height - y_offset; + } + + gdk_pixbuf_composite (source, destination, x_offset, y_offset, source_width, source_height, + float_x_offset, float_y_offset, 1.0, 1.0, GDK_PIXBUF_ALPHA_BILEVEL, alpha); +} + + + +/* draw the logo image */ +static int +draw_rss_logo_image (NautilusRSSControl *rss_control, GdkPixbuf *pixbuf, int offset) +{ + GtkWidget *widget; + char time_str[16]; + int logo_width, logo_height; + int v_offset, pixbuf_width; + EelDimensions time_dimensions; + + widget = GTK_WIDGET (rss_control); + v_offset = offset; + + if (rss_control->details->logo != NULL) { + logo_width = gdk_pixbuf_get_width (rss_control->details->logo); + logo_height = gdk_pixbuf_get_height (rss_control->details->logo); + + rss_control_pixbuf_composite (rss_control->details->logo, pixbuf, 2, v_offset, 255); + v_offset += logo_height + 2; + } + + /* also, read the update time in the upper right corner */ + if (rss_control->details->last_update != 0) { + strftime (&time_str[0], 16, "%I:%M %p", localtime (&rss_control->details->last_update)); + + time_dimensions = eel_scalable_font_measure_text (rss_control->details->font, 9, time_str, strlen (time_str)); + + pixbuf_width = gdk_pixbuf_get_width (pixbuf); + eel_scalable_font_draw_text (rss_control->details->font, pixbuf, + pixbuf_width - time_dimensions.width - 4, offset, + NULL, 9, time_str, strlen (time_str), + EEL_RGB_COLOR_BLACK, EEL_OPACITY_FULLY_OPAQUE); + } + return v_offset; +} + +/* draw the title */ +static int +draw_rss_title (NautilusRSSControl *rss_control, GdkPixbuf *pixbuf, int v_offset) +{ + GtkWidget *widget; + EelDimensions title_dimensions; + + if (rss_control->details->title == NULL || rss_control->details->font == NULL) { + return v_offset; + } + + widget = GTK_WIDGET (rss_control); + + /* first, measure the text */ + title_dimensions = eel_scalable_font_measure_text (rss_control->details->font, + 18, + rss_control->details->title, strlen (rss_control->details->title)); + + /* draw the name into the pixbuf using anti-aliased text */ + eel_scalable_font_draw_text (rss_control->details->font, pixbuf, + 4, v_offset, + NULL, + 18, + rss_control->details->title, strlen (rss_control->details->title), + EEL_RGB_COLOR_BLACK, + EEL_OPACITY_FULLY_OPAQUE); + + return v_offset + title_dimensions.height; +} + +/* utility for underlining an item - assumes the pixbuf has an alpha channel */ +static void +draw_blue_line (GdkPixbuf *pixbuf, int x, int y, int width) +{ + guchar *pixels_ptr; + int row_stride, line_width, pixbuf_width, i; + + line_width = width; + pixbuf_width = gdk_pixbuf_get_width (pixbuf); + if ((x + line_width) > pixbuf_width) { + line_width = pixbuf_width - x - 1; + } + row_stride = gdk_pixbuf_get_rowstride (pixbuf); + pixels_ptr = gdk_pixbuf_get_pixels (pixbuf); + + pixels_ptr += (4 * x) + (row_stride * y); + for (i = 0; i < line_width; i++) { + *pixels_ptr++ = 0; + *pixels_ptr++ = 0; + *pixels_ptr++ = 159; + *pixels_ptr++ = 255; + + } +} + +/* draw the items */ +static int +draw_rss_items (NautilusRSSControl *rss_control, GdkPixbuf *pixbuf, int v_offset) +{ + GList *current_item; + RSSItemData *item_data; + int bullet_width, bullet_height, font_size; + int item_index, bullet_alpha; + int maximum_height; + guint32 text_color; + EelDimensions text_dimensions; + + maximum_height = GTK_WIDGET (rss_control)->allocation.height - 16; + + if (rss_control->details->bullet) { + bullet_width = gdk_pixbuf_get_width (rss_control->details->bullet); + bullet_height = gdk_pixbuf_get_height (rss_control->details->bullet); + } else { + bullet_width = 0; + bullet_height = 0; + } + + current_item = rss_control->details->items; + item_index = 0; + + while (current_item != NULL) { + /* draw the text */ + + item_data = (RSSItemData*) current_item->data; + if (item_index == rss_control->details->prelight_index) { + text_color = EEL_RGB_COLOR_BLUE; + bullet_alpha = 255; + } else { + text_color = EEL_RGB_COLOR_BLACK; + bullet_alpha = 160; + } + font_size = 12; + + text_dimensions = eel_scalable_font_measure_text (rss_control->details->font, + font_size, + item_data->item_title, + strlen (item_data->item_title)); + + eel_scalable_font_draw_text (rss_control->details->font, pixbuf, + 20, v_offset, + NULL, + font_size, + item_data->item_title, strlen (item_data->item_title), + text_color, + EEL_OPACITY_FULLY_OPAQUE); + + /* draw a blue underline to make it look like a link */ + draw_blue_line (pixbuf, 20, v_offset + 11, text_dimensions.width); + + /* draw the bullet */ + if (rss_control->details->bullet) { + rss_control_pixbuf_composite (rss_control->details->bullet, pixbuf, 2, v_offset - 2, bullet_alpha); + } + + v_offset += RSS_ITEM_HEIGHT; + item_index += 1; + current_item = current_item->next; + if (v_offset > maximum_height) { + break; + } + + } + + return v_offset; +} + +/* handle drawing the control */ +static void +nautilus_rss_control_draw (GtkWidget *widget, GdkRectangle *box) +{ + NautilusRSSControl *control; + GdkPixbuf *temp_pixbuf; + ArtIRect pixbuf_rect; + + int width, height, v_offset; + + /* allocate a pixbuf to draw into */ + width = widget->allocation.width; + height = widget->allocation.height; + + /* don't draw when too small, like during size negotiation */ + if (width < MINIMUM_DRAW_SIZE || height < MINIMUM_DRAW_SIZE) { + return; + } + + temp_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, width, height); + pixbuf_rect.x0 = 1; + pixbuf_rect.y0 = 1; + pixbuf_rect.x1 = width - 1; + pixbuf_rect.y1 = height - 1; + + eel_gdk_pixbuf_fill_rectangle_with_color (temp_pixbuf, NULL, 0xFF000000); + eel_gdk_pixbuf_fill_rectangle_with_color (temp_pixbuf, &pixbuf_rect, 0xFFEFEFEF); + + g_return_if_fail (widget != NULL); + g_return_if_fail (NAUTILUS_IS_RSS_CONTROL (widget)); + + control = NAUTILUS_RSS_CONTROL (widget); + + v_offset = draw_rss_logo_image (control, temp_pixbuf, 2); + v_offset = draw_rss_title (control, temp_pixbuf, v_offset); + control->details->items_v_offset = v_offset; + + v_offset += 6; + v_offset = draw_rss_items (control, temp_pixbuf, v_offset); + + /* blit the pixbuf to the drawable, then release it */ + gdk_pixbuf_render_to_drawable_alpha (temp_pixbuf, + widget->window, + 0, 0, + widget->allocation.x, widget->allocation.y, + width, height, + GDK_PIXBUF_ALPHA_BILEVEL, 128, + GDK_RGB_DITHER_MAX, + 0, 0); + + gdk_pixbuf_unref (temp_pixbuf); +} + +/* handle expose events */ +static int +nautilus_rss_control_expose (GtkWidget *widget, GdkEventExpose *event) +{ + GdkRectangle box; + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (NAUTILUS_IS_RSS_CONTROL (widget), FALSE); + + box.x = 0; box.y = 0; + box.width = widget->allocation.width; + box.height = widget->allocation.height; + + nautilus_rss_control_draw (widget, &box); + return FALSE; +} + +/* maintain the prelight state, redrawing if necessary */ + +static void +nautilus_rss_control_set_prelight_index (NautilusRSSControl *rss_control, int prelight_state) +{ + if (rss_control->details->prelight_index != prelight_state) { + rss_control->details->prelight_index = prelight_state; + gtk_widget_queue_draw (GTK_WIDGET (rss_control)); + } +} + +/* handle mouse motion events by maintaining the prelight state */ +static gboolean +nautilus_rss_control_motion_event (GtkWidget *widget, GdkEventMotion *event) +{ + int x, y; + int which_item, item_count; + NautilusRSSControl *rss_control; + + rss_control = NAUTILUS_RSS_CONTROL (widget); + + gtk_widget_get_pointer (widget, &x, &y); + which_item = (y - (widget->allocation.y + rss_control->details->items_v_offset)) / RSS_ITEM_HEIGHT; + item_count = g_list_length (rss_control->details->items); + + if (which_item < 0 || which_item >= item_count) { + which_item = -1; + } + nautilus_rss_control_set_prelight_index (rss_control, which_item); + return TRUE; +} + +/* handle leave events by cancelling any prelighting */ +static gboolean +nautilus_rss_control_leave_event (GtkWidget *widget, GdkEventCrossing *event) +{ + NautilusRSSControl *rss_control; + + rss_control = NAUTILUS_RSS_CONTROL (widget); + nautilus_rss_control_set_prelight_index (rss_control, -1); + + return TRUE; +} + +/* handle size requests by requesting a fixed size */ +/* should size according to the number of items */ +static void +nautilus_rss_control_size_request (GtkWidget *widget, GtkRequisition *request) +{ + request->width = 240; + request->height = 140; +} + + +/* handle button press events */ +static gboolean +nautilus_rss_control_button_press_event (GtkWidget *widget, GdkEventButton *event) +{ + GList *selected_item; + NautilusRSSControl *rss_control; + RSSItemData *item_data; + char *command; + int result, which_item; + + rss_control = NAUTILUS_RSS_CONTROL (widget); + if (event->y < (widget->allocation.y + rss_control->details->items_v_offset)) { + command = g_strdup_printf ("nautilus %s", rss_control->details->main_uri); + result = system (command); + g_free (command); + } else { + which_item = (event->y - (widget->allocation.y + rss_control->details->items_v_offset)) / RSS_ITEM_HEIGHT; + if (which_item < (int) g_list_length (rss_control->details->items)) { + selected_item = g_list_nth (rss_control->details->items, which_item); + item_data = (RSSItemData*) selected_item->data; + + command = g_strdup_printf ("nautilus %s", item_data->item_url); + result = system (command); + g_free (command); + } + } + + return FALSE; +} + diff --git a/components/rss-control/nautilus-rss-control.h b/components/rss-control/nautilus-rss-control.h new file mode 100644 index 000000000..e73c31bbc --- /dev/null +++ b/components/rss-control/nautilus-rss-control.h @@ -0,0 +1,54 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* + * Copyright (C) 2000 Eazel, Inc + * + * 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: Andy Hertzfeld + */ + +/* header file for the rss control component */ + +#ifndef NAUTILUS_RSS_CONTROL_H +#define NAUTILUS_RSS_CONTROL_H + +#include <bonobo.h> +#include <gtk/gtkeventbox.h> +typedef struct _NautilusRSSControl NautilusRSSControl; +typedef struct _NautilusRSSControlClass NautilusRSSControlClass; + +#define NAUTILUS_TYPE_RSS_CONTROL (nautilus_rss_control_get_type ()) +#define NAUTILUS_RSS_CONTROL(obj) (GTK_CHECK_CAST ((obj), NAUTILUS_TYPE_RSS_CONTROL, NautilusRSSControl)) +#define NAUTILUS_RSS_CONTROL_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_RSS_CONTROL, NautilusRSSControlClass)) +#define NAUTILUS_IS_RSS_CONTROL(obj) (GTK_CHECK_TYPE ((obj), NAUTILUS_TYPE_RSS_CONTROL)) +#define NAUTILUS_IS_RSS_CONTROL_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_RSS_CONTROL)) + +typedef struct _NautilusRSSControlDetails NautilusRSSControlDetails; + +struct _NautilusRSSControl { + GtkEventBox parent; + NautilusRSSControlDetails *details; +}; + +struct _NautilusRSSControlClass { + GtkEventBoxClass parent_class; +}; + +/* GtkObject support */ +GtkType nautilus_rss_control_get_type (void); +BonoboObject* nautilus_rss_control_get_control (NautilusRSSControl *rss_control); + +#endif /* NAUTILUS_RSS_CONTROL_H */ diff --git a/components/rss-control/nautilus-rss-control.oafinfo b/components/rss-control/nautilus-rss-control.oafinfo new file mode 100644 index 000000000..9d21acf6a --- /dev/null +++ b/components/rss-control/nautilus-rss-control.oafinfo @@ -0,0 +1,20 @@ +<oaf_info> + +<oaf_server iid="OAFIID:nautilus_rss_control_factory:1230" type="exe" location="nautilus-rss-control"> +<oaf_attribute name="repo_ids" type="stringv"> +<item value="IDL:Bonobo/GenericFactory:1.0"/> +</oaf_attribute> +<oaf_attribute name="name" type="string" value="rss control factory"/> +<oaf_attribute name="description" type="string" value="rss control object factory"/> +</oaf_server> + +<oaf_server iid="OAFIID:nautilus_rss_control:1230" type="factory" location="OAFIID:nautilus_rss_control_factory:1230"> +<oaf_attribute name="repo_ids" type="stringv"> +<item value="IDL:Bonobo/Control:1.0"/> +<item value="IDL:Bonobo/Unknown:1.0"/> +</oaf_attribute> +<oaf_attribute name="name" type="string" value="rss control"/> +<oaf_attribute name="description" type="string" value="nautilus rss control object"/> +</oaf_server> + +</oaf_info> diff --git a/components/services/nautilus-dependent-shared/shared-service-widgets.c b/components/services/nautilus-dependent-shared/shared-service-widgets.c deleted file mode 100644 index c88adcbd9..000000000 --- a/components/services/nautilus-dependent-shared/shared-service-widgets.c +++ /dev/null @@ -1,212 +0,0 @@ -/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ - -/* - * Copyright (C) 2000 Eazel, Inc - * - * 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: Ramiro Estrugo <ramiro@eazel.com> - * J Shane Culpepper <pepper@eazel.com> - * - */ - -#include <config.h> - -#include "shared-service-widgets.h" - -#include <libnautilus-extensions/nautilus-background.h> -#include <libnautilus-extensions/nautilus-gdk-pixbuf-extensions.h> -#include <libnautilus-extensions/nautilus-gtk-extensions.h> -#include <libnautilus-extensions/nautilus-gtk-macros.h> -#include <libnautilus-extensions/nautilus-glib-extensions.h> -#include <libnautilus-extensions/nautilus-global-preferences.h> -#include <libnautilus-extensions/nautilus-file-utilities.h> -#include <libnautilus-extensions/nautilus-string.h> -#include <libnautilus-extensions/nautilus-font-factory.h> -#include <libnautilus-extensions/nautilus-gdk-extensions.h> -#include <libnautilus-extensions/nautilus-theme.h> - -#include <stdio.h> - -/* private shared helper routine to create an image widget from a pixbuf */ -static GtkWidget* -create_image_widget_from_pixbuf (GdkPixbuf *icon_pixbuf, - const char *tile_icon_name) -{ - GtkWidget *image_widget; - - g_return_val_if_fail (icon_pixbuf || tile_icon_name, NULL); - - image_widget = nautilus_image_new (); - - if (icon_pixbuf != NULL) { - nautilus_image_set_pixbuf (NAUTILUS_IMAGE (image_widget), icon_pixbuf); - } - - if (tile_icon_name != NULL) { - char *tile_icon_path; - - tile_icon_path = nautilus_pixmap_file (tile_icon_name); - - if (tile_icon_path != NULL) { - GdkPixbuf *tile_icon_pixbuf; - tile_icon_pixbuf = gdk_pixbuf_new_from_file (tile_icon_path); - g_free (tile_icon_path); - - if (tile_icon_pixbuf != NULL) { - nautilus_buffered_widget_set_tile_pixbuf (NAUTILUS_BUFFERED_WIDGET (image_widget), tile_icon_pixbuf); - gdk_pixbuf_unref (tile_icon_pixbuf); - } - else { - g_warning ("Could not find the requested tile_icon: %s", tile_icon_path); - } - } - } - - return image_widget; -} - -/* create and return an image widget using a themed nautilus icon name and a tiled background */ -GtkWidget* -create_image_widget (const char *icon_name, const char *tile_icon_name) -{ - GtkWidget *image_widget; - GdkPixbuf *pixbuf; - - g_return_val_if_fail (icon_name || tile_icon_name, NULL); - - pixbuf = NULL; - if (icon_name != NULL) { - char *icon_path; - - icon_path = nautilus_theme_get_image_path (icon_name); - if (icon_path != NULL) { - pixbuf = gdk_pixbuf_new_from_file (icon_path); - g_free (icon_path); - - if (pixbuf == NULL) { - g_warning ("Could not find the requested icon: %s", icon_path); - } - } - } - - /* create the image widget then release the pixbuf*/ - image_widget = create_image_widget_from_pixbuf (pixbuf, tile_icon_name); - if (pixbuf != NULL) { - gdk_pixbuf_unref (pixbuf); - } - return image_widget; -} - -/* create and return an image widget from a uri and a tiled background. - It also pins the image to the specified dimensions */ - -/* FIXME bugzilla.eazel.com 5138 - * this calls gnome-vfs synchronously for an HTTP uri and thus can block - * the UI indefinitely - */ -GtkWidget* -create_image_widget_from_uri (const char *uri, const char *tile_icon_name, - int max_width, int max_height) -{ - GtkWidget *image_widget; - GdkPixbuf *pixbuf, *scaled_pixbuf; - - g_return_val_if_fail (uri || tile_icon_name, NULL); - - /* as an optimization, it can be a local file. If it doesn't start with http://, - just pass it on to create_image_widget */ - if (!nautilus_istr_has_prefix (uri, "http://")) { - return create_image_widget (uri, tile_icon_name); - } - - /* load the image - synchronously, at least at first */ - pixbuf = nautilus_gdk_pixbuf_load (uri); - - /* pin the image to the specified dimensions if necessary */ - if (pixbuf && max_width > 0 && max_height > 0) { - scaled_pixbuf = nautilus_gdk_pixbuf_scale_down_to_fit (pixbuf, max_width, max_height); - gdk_pixbuf_unref (pixbuf); - pixbuf = scaled_pixbuf; - } - - /* create the image widget then release the pixbuf*/ - image_widget = create_image_widget_from_pixbuf (pixbuf, tile_icon_name); - if (pixbuf != NULL) { - gdk_pixbuf_unref (pixbuf); - } - - return image_widget; -} - -/* create a label widget with anti-aliased text and a tiled image background */ -GtkWidget* -create_label_widget (const char *text, - guint font_size, - const char *tile_icon_name, - guint xpad, - guint ypad, - gint horizontal_offset, - gint vertical_offset) -{ - GtkWidget *label; - - g_return_val_if_fail (text != NULL, NULL); - g_return_val_if_fail (font_size > 0, NULL); - - label = nautilus_label_new (text); - - nautilus_label_set_font_from_components (NAUTILUS_LABEL (label), "helvetica", "bold", NULL, NULL); - nautilus_label_set_font_size (NAUTILUS_LABEL (label), font_size); - nautilus_label_set_text_color (NAUTILUS_LABEL (label), NAUTILUS_RGB_COLOR_WHITE); - - if (tile_icon_name != NULL) { - char *tile_icon_path; - - tile_icon_path = nautilus_pixmap_file (tile_icon_name); - - if (tile_icon_path != NULL) { - GdkPixbuf *tile_icon_pixbuf; - tile_icon_pixbuf = gdk_pixbuf_new_from_file (tile_icon_path); - g_free (tile_icon_path); - - if (tile_icon_pixbuf != NULL) { - nautilus_buffered_widget_set_tile_pixbuf (NAUTILUS_BUFFERED_WIDGET (label), tile_icon_pixbuf); - gdk_pixbuf_unref (tile_icon_pixbuf); - } - else { - g_warning ("Could not find the requested tile_icon: %s", tile_icon_path); - } - } - } - - gtk_misc_set_padding (GTK_MISC (label), xpad, ypad); - - nautilus_buffered_widget_set_vertical_offset (NAUTILUS_BUFFERED_WIDGET (label), vertical_offset); - nautilus_buffered_widget_set_horizontal_offset (NAUTILUS_BUFFERED_WIDGET (label), horizontal_offset); - - return label; -} - -/* utility routine to show an error message */ -void -show_feedback (GtkWidget *widget, - char *error_message) -{ - nautilus_label_set_text (NAUTILUS_LABEL (widget), error_message); - gtk_widget_show (widget); -} - diff --git a/components/throbber/nautilus-throbber.c b/components/throbber/nautilus-throbber.c index f86d7513e..a3b82cb95 100644 --- a/components/throbber/nautilus-throbber.c +++ b/components/throbber/nautilus-throbber.c @@ -104,7 +104,8 @@ nautilus_throbber_initialize_class (NautilusThrobberClass *throbber_class) enum { THROBBING, - LOCATION + LOCATION, + CONFIGURATION } MyArgs; @@ -140,8 +141,12 @@ get_bonobo_properties (BonoboPropertyBag *bag, BONOBO_ARG_SET_STRING (arg, ""); } + break; } + case CONFIGURATION: + BONOBO_ARG_SET_STRING (arg, ""); + break; default: g_warning ("Unhandled arg %d", arg_id); break; @@ -174,6 +179,13 @@ set_bonobo_properties (BonoboPropertyBag *bag, break; } + /* respond to configuration calls by starting the throbber */ + case CONFIGURATION: + { + nautilus_throbber_start (throbber); + break; + } + default: g_warning ("Unhandled arg %d", arg_id); break; @@ -300,6 +312,9 @@ nautilus_throbber_initialize (NautilusThrobber *throbber) "Throbber active", 0); bonobo_property_bag_add (throbber->details->property_bag, "location", LOCATION, BONOBO_ARG_STRING, NULL, "associated URL", 0); + + bonobo_property_bag_add (throbber->details->property_bag, "configuration", CONFIGURATION, BONOBO_ARG_STRING, NULL, + "Throbber image source", BONOBO_PROPERTY_WRITEABLE); /* allocate the pixmap that holds the image */ nautilus_throbber_load_images (throbber); diff --git a/components/vcard/.cvsignore b/components/vcard/.cvsignore new file mode 100644 index 000000000..3c5d6aaee --- /dev/null +++ b/components/vcard/.cvsignore @@ -0,0 +1,5 @@ +.deps +.libs +Makefile +Makefile.in +nautilus-vcard diff --git a/components/vcard/Makefile.am b/components/vcard/Makefile.am new file mode 100644 index 000000000..0b66e828f --- /dev/null +++ b/components/vcard/Makefile.am @@ -0,0 +1,51 @@ +NULL = + +SUBDIRS = + +INCLUDES = \ + -DPREFIX=\"$(prefix)\" \ + -DG_LOG_DOMAIN=\"Nautilus-VCard\" \ + -DDATADIR=\""$(datadir)"\" \ + -I$(top_srcdir) \ + -I$(top_builddir) \ + -I$(top_builddir)/libnautilus \ + -DGNOMELOCALEDIR=\""$(datadir)/locale"\" \ + $(EEL_INCLUDEDIR) \ + $(LIBRSVG_INCLUDEDIR) \ + $(GNOMEUI_CFLAGS) \ + $(GCONF_CFLAGS) \ + $(OAF_CFLAGS) \ + $(BONOBO_CFLAGS) \ + $(VFS_CFLAGS) + +oafdir = $(datadir)/oaf +oaf_DATA = \ + nautilus-vcard.oaf + + +bin_PROGRAMS = \ + nautilus-vcard + +nautilus_vcard_SOURCES = \ + vcard.c \ + vcard.h \ + nautilus-vcard.c \ + nautilus-vcard.h \ + main.c + +nautilus_vcard_LDADD = \ + $(top_builddir)/libnautilus/libnautilus.la \ + $(top_builddir)/libnautilus-extensions/libnautilus-extensions.la \ + $(EEL_LIBS) \ + $(LIBRSVG_LIBS) \ + $(BONOBO_LIBS) \ + $(GNOMEUI_LIBS) \ + $(GCONF_LIBS) \ + $(VFS_LIBS) \ + $(GNORBA_LIBS) + +OBJECT_DIRECTORY_LIBS = $(GNOME_LIBS) $(OAF_LIBS) + +EXTRA_DIST = \ + $(oaf_DATA) \ + $(NULL) diff --git a/components/vcard/main.c b/components/vcard/main.c new file mode 100644 index 000000000..68f1e3334 --- /dev/null +++ b/components/vcard/main.c @@ -0,0 +1,120 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* + * Copyright (C) 2000 Eazel, Inc + * + * 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: Andy Hertzfeld + */ + +/* main.c - main function and object activation function for the vcard component. */ + +#include <config.h> +#include "nautilus-vcard.h" + +#include <bonobo.h> +#include <gnome.h> +#include <libgnomevfs/gnome-vfs.h> +#include <eel/eel-debug.h> +#include <liboaf/liboaf.h> + +static int object_count = 0; + +static void +vcard_object_destroyed(GtkObject *obj) +{ + object_count--; + if (object_count <= 0) { + gtk_main_quit (); + } +} + +static BonoboObject * +vcard_make_object (BonoboGenericFactory *factory, + const char *iid, + void *closure) +{ + NautilusVCard *vcard; + BonoboObject *bonobo_control; + + if (strcmp (iid, "OAFIID:nautilus_vcard")) { + return NULL; + } + + vcard = NAUTILUS_VCARD (gtk_object_new (NAUTILUS_TYPE_VCARD, NULL)); + + object_count++; + + bonobo_control = nautilus_vcard_get_control (vcard); + + gtk_signal_connect (GTK_OBJECT (bonobo_control), "destroy", vcard_object_destroyed, NULL); + return bonobo_control; +} + +int +main (int argc, char *argv[]) +{ + BonoboGenericFactory *factory; + CORBA_ORB orb; + char *registration_id; + + /* Make criticals and warnings stop in the debugger if + * NAUTILUS_DEBUG is set. Unfortunately, this has to be done + * explicitly for each domain. + */ + if (g_getenv ("NAUTILUS_DEBUG") != NULL) { + eel_make_warnings_and_criticals_stop_in_debugger + (G_LOG_DOMAIN, g_log_domain_glib, + "Bonobo", + "Gdk", + "GnomeUI", + "GnomeVFS", + "GnomeVFS-CORBA", + "GnomeVFS-pthread", + "Gtk", + "Gdk-Pixbuf", + "Nautilus", + "Nautilus-Authenticate", + "Nautilus-Tree", + "ORBit", + NULL); + } + + gnome_init_with_popt_table("nautilus-vcard", VERSION, + argc, argv, + oaf_popt_options, 0, NULL); + + orb = oaf_init (argc, argv); + + bonobo_init (orb, CORBA_OBJECT_NIL, CORBA_OBJECT_NIL); + + /* initialize gnome-vfs, etc */ + g_thread_init (NULL); + gnome_vfs_init (); + + registration_id = oaf_make_registration_id ("OAFIID:nautilus_vcard_factory", getenv ("DISPLAY")); + factory = bonobo_generic_factory_new_multi (registration_id, + vcard_make_object, + NULL); + g_free (registration_id); + + + do { + bonobo_main (); + } while (object_count > 0); + + return 0; +} diff --git a/components/vcard/nautilus-vcard.c b/components/vcard/nautilus-vcard.c new file mode 100644 index 000000000..e22002c79 --- /dev/null +++ b/components/vcard/nautilus-vcard.c @@ -0,0 +1,577 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */ + +/* + * Copyright (C) 2000 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 <andy@eazel.com> + * + */ + +/* this is the implementation of the vcard component, which display a vcard graphically + */ + +#include <config.h> +#include <gnome.h> +#include <liboaf/liboaf.h> + +#include <bonobo.h> + +#include "vcard.h" +#include "nautilus-vcard.h" + +#include <gnome-xml/parser.h> +#include <gnome-xml/xmlmemory.h> + +#include <libgnomevfs/gnome-vfs.h> + +#include <libnautilus/nautilus-view.h> + +#include <libnautilus-extensions/nautilus-file-utilities.h> +#include <eel/eel-gdk-extensions.h> +#include <eel/eel-gdk-pixbuf-extensions.h> +#include <eel/eel-glib-extensions.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-gtk-macros.h> +#include <eel/eel-scalable-font.h> +#include <eel/eel-string.h> +#include <eel/eel-xml-extensions.h> +#include <eel/eel-vfs-extensions.h> +#include <libnautilus-extensions/nautilus-font-factory.h> + +/* private instance variables */ +struct _NautilusVCardDetails { + char *vcard_uri; + char *vcard_data; + + EelReadFileHandle *load_file_handle; + EelPixbufLoadHandle *load_image_handle; + + BonoboObject *control; + EelScalableFont *font; + + GdkPixbuf *logo; +}; + +static void nautilus_vcard_initialize_class (NautilusVCardClass *klass); +static void nautilus_vcard_initialize (NautilusVCard *view); +static void nautilus_vcard_destroy (GtkObject *object); + +static void nautilus_vcard_draw (GtkWidget *widget, GdkRectangle *box); +static int nautilus_vcard_expose (GtkWidget *widget, GdkEventExpose *event); +static gboolean nautilus_vcard_button_press_event (GtkWidget *widget, GdkEventButton *event); +static gboolean nautilus_vcard_motion_event (GtkWidget *widget, GdkEventMotion *event); +static gboolean nautilus_vcard_leave_event (GtkWidget *widget, GdkEventCrossing *event); +static void nautilus_vcard_size_request (GtkWidget *widget, GtkRequisition *request); + +static void nautilus_vcard_set_uri (NautilusVCard *vcard, const char *uri); + + +EEL_DEFINE_CLASS_BOILERPLATE (NautilusVCard, + nautilus_vcard, + GTK_TYPE_EVENT_BOX) + +#define MINIMUM_DRAW_SIZE 4 + +static void +nautilus_vcard_initialize_class (NautilusVCardClass *klass) +{ + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + + object_class = GTK_OBJECT_CLASS (klass); + widget_class = GTK_WIDGET_CLASS (klass); + + object_class->destroy = nautilus_vcard_destroy; + + widget_class->draw = nautilus_vcard_draw; + widget_class->expose_event = nautilus_vcard_expose; + widget_class->button_press_event = nautilus_vcard_button_press_event; + widget_class->motion_notify_event = nautilus_vcard_motion_event; + widget_class->leave_notify_event = nautilus_vcard_leave_event; + widget_class->size_request = nautilus_vcard_size_request; +} + +/* routines to handle setting and getting the configuration properties of the Bonobo control */ + +enum { + CONFIGURATION +} MyArgs; + + +static void +get_bonobo_properties (BonoboPropertyBag *bag, + BonoboArg *arg, + guint arg_id, + CORBA_Environment *ev, + gpointer user_data) +{ + NautilusVCard *vcard = NAUTILUS_VCARD (user_data); + + switch (arg_id) { + + case CONFIGURATION: + { + BONOBO_ARG_SET_STRING (arg, vcard->details->vcard_uri); + break; + } + + default: + g_warning ("Unhandled arg %d", arg_id); + break; + } +} + +static void +set_bonobo_properties (BonoboPropertyBag *bag, + const BonoboArg *arg, + guint arg_id, + CORBA_Environment *ev, + gpointer user_data) +{ + NautilusVCard *vcard = NAUTILUS_VCARD (user_data); + + switch (arg_id) { + + case CONFIGURATION: + { + char *uri; + + uri = BONOBO_ARG_GET_STRING (arg); + nautilus_vcard_set_uri (vcard, uri); + + break; + } + + default: + g_warning ("Unhandled arg %d", arg_id); + break; + } +} + +/* initialize ourselves by connecting to the location change signal and allocating our subviews */ +static void +nautilus_vcard_initialize (NautilusVCard *vcard) +{ + GtkWidget *frame; + BonoboPropertyBag *property_bag; + + vcard->details = g_new0 (NautilusVCardDetails, 1); + + /* set up the font */ + vcard->details->font = eel_scalable_font_get_default_font (); + + /* receive mouse motion events */ + gtk_widget_add_events (GTK_WIDGET (vcard), GDK_POINTER_MOTION_MASK); + + /* embed it into a frame */ + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type(GTK_FRAME (frame), GTK_SHADOW_OUT); + gtk_widget_show (frame); + gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (vcard)); + + /* make the bonobo control */ + vcard->details->control = (BonoboObject*) bonobo_control_new (GTK_WIDGET (frame)); + + /* attach a property bag with the configure property */ + property_bag = bonobo_property_bag_new (get_bonobo_properties, set_bonobo_properties, vcard); + bonobo_control_set_properties (BONOBO_CONTROL(vcard->details->control),property_bag); + bonobo_object_unref (BONOBO_OBJECT (property_bag)); + + bonobo_property_bag_add (property_bag, "configuration", CONFIGURATION, BONOBO_ARG_STRING, NULL, + "VCard Configuration", BONOBO_PROPERTY_WRITEABLE); + + /* show the view itself */ + gtk_widget_show (GTK_WIDGET (vcard)); +} + + +static void +nautilus_vcard_destroy (GtkObject *object) +{ + NautilusVCard *vcard; + + vcard = NAUTILUS_VCARD (object); + g_free (vcard->details->vcard_uri); + g_free (vcard->details->vcard_data); + + if (vcard->details->load_file_handle != NULL) { + eel_read_file_cancel (vcard->details->load_file_handle); + } + + if (vcard->details->load_image_handle != NULL) { + eel_cancel_gdk_pixbuf_load (vcard->details->load_image_handle); + } + + + if (vcard->details->logo != NULL) { + gdk_pixbuf_unref (vcard->details->logo); + } + + if (vcard->details->font) { + gtk_object_unref (GTK_OBJECT (vcard->details->font)); + } + + g_free (vcard->details); + + /* Chain destroy */ + EEL_CALL_PARENT (GTK_OBJECT_CLASS, destroy, (object)); +} + +/* get associated Bonobo control */ +BonoboObject * +nautilus_vcard_get_control (NautilusVCard *vcard) +{ + return vcard->details->control; +} + +static void +vcard_logo_callback (GnomeVFSResult error, GdkPixbuf *pixbuf, gpointer callback_data) +{ + NautilusVCard *vcard; + GdkPixbuf *scaled_pixbuf; + + vcard = NAUTILUS_VCARD (callback_data); + vcard->details->load_image_handle = NULL; + + if (vcard->details->logo) { + gdk_pixbuf_unref (vcard->details->logo); + } + + if (pixbuf != NULL) { + gdk_pixbuf_ref (pixbuf); + + scaled_pixbuf = eel_gdk_pixbuf_scale_down_to_fit (pixbuf, 128, 80); + gdk_pixbuf_unref (pixbuf); + vcard->details->logo = scaled_pixbuf; + gtk_widget_queue_draw (GTK_WIDGET (vcard)); + } +} + +/* completion routine invoked when we've loaded the vcard file uri. */ + +static void +vcard_read_done_callback (GnomeVFSResult result, + GnomeVFSFileSize file_size, + char *file_contents, + gpointer callback_data) +{ + char *logo_uri; + NautilusVCard *vcard; + + vcard = NAUTILUS_VCARD (callback_data); + vcard->details->load_file_handle = NULL; + + /* make sure the read was successful */ + if (result != GNOME_VFS_OK) { + g_assert (file_contents == NULL); + return; + } + + /* free old data if any */ + if (vcard->details->vcard_data) { + g_free (vcard->details->vcard_data); + } + + /* set up the vcard data */ + vcard->details->vcard_data = g_realloc (file_contents, file_size + 1); + vcard->details->vcard_data[file_size] = '\0'; + + /* extract the image uri and, if found, load it asynchronously */ + logo_uri = vcard_logo (vcard->details->vcard_data); + g_message ("logo uri is %s", logo_uri); + if (logo_uri != NULL) { + vcard->details->load_image_handle = eel_gdk_pixbuf_load_async (logo_uri, vcard_logo_callback, vcard); + g_free (logo_uri); + } + + /* schedule a redraw to reflect the new contents */ + gtk_widget_queue_draw (GTK_WIDGET (vcard)); +} + +/* load the vcard asynchronously */ +static void +load_vcard (NautilusVCard *vcard) +{ + /* load the uri asynchrounously, calling a completion routine when completed */ + vcard->details->load_file_handle = eel_read_entire_file_async (vcard->details->vcard_uri, vcard_read_done_callback, vcard); +} + +/* set the uri and load it */ +static void +nautilus_vcard_set_uri (NautilusVCard *vcard, const char *uri) +{ + + if (eel_strcmp (vcard->details->vcard_uri, uri) == 0) { + return; + } + + if (vcard->details->vcard_uri != NULL) { + g_free (vcard->details->vcard_uri); + vcard->details->vcard_uri = NULL; + } + + if (uri != NULL) { + vcard->details->vcard_uri = g_strdup (uri); + load_vcard (vcard); + } +} + +/* convenience routine to composite an image with the proper clipping */ +static void +vcard_pixbuf_composite (GdkPixbuf *source, GdkPixbuf *destination, int x_offset, int y_offset, int alpha) +{ + int source_width, source_height, dest_width, dest_height; + double float_x_offset, float_y_offset; + + source_width = gdk_pixbuf_get_width (source); + source_height = gdk_pixbuf_get_height (source); + dest_width = gdk_pixbuf_get_width (destination); + dest_height = gdk_pixbuf_get_height (destination); + + float_x_offset = x_offset; + float_y_offset = y_offset; + + /* clip to the destination size */ + if ((x_offset + source_width) > dest_width) { + source_width = dest_width - x_offset; + } + if ((y_offset + source_height) > dest_height) { + source_height = dest_height - y_offset; + } + + gdk_pixbuf_composite (source, destination, x_offset, y_offset, source_width, source_height, + float_x_offset, float_y_offset, 1.0, 1.0, GDK_PIXBUF_ALPHA_BILEVEL, alpha); +} + + + +/* draw the logo image */ +static int +draw_vcard_logo_image (NautilusVCard *vcard, GdkPixbuf *pixbuf, int offset) +{ + GtkWidget *widget; + int logo_width, logo_height; + int v_offset; + + widget = GTK_WIDGET (vcard); + v_offset = offset; + + if (vcard->details->logo != NULL) { + logo_width = gdk_pixbuf_get_width (vcard->details->logo); + logo_height = gdk_pixbuf_get_height (vcard->details->logo); + + vcard_pixbuf_composite (vcard->details->logo, pixbuf, 2, v_offset, 255); + v_offset += logo_height + 2; + } + + return v_offset; +} + +/* draw the name and title */ +static int +draw_vcard_name_and_title (NautilusVCard *vcard, GdkPixbuf *pixbuf, int v_offset) +{ + int name_len, title_len; + char *name, *title; + GtkWidget *widget; + EelDimensions name_dimensions; + EelDimensions title_dimensions; + + if (vcard->details->font == NULL) { + return v_offset; + } + + widget = GTK_WIDGET (vcard); + + /* extract the name and title */ + name = vcard_full_name (vcard->details->vcard_data); + title = vcard_title (vcard->details->vcard_data); + + /* first, measure the name */ + if (name != NULL) { + name_len = strlen (name); + name_dimensions = eel_scalable_font_measure_text (vcard->details->font, + 18, + name, name_len); + + /* draw the name into the pixbuf using anti-aliased text */ + eel_scalable_font_draw_text (vcard->details->font, pixbuf, + 4, v_offset, + NULL, + 18, + name, name_len, + EEL_RGB_COLOR_BLACK, + EEL_OPACITY_FULLY_OPAQUE); + v_offset += name_dimensions.height + 4; + + g_free (name); + + if (title != NULL) { + title_len = strlen (title); + title_dimensions = eel_scalable_font_measure_text (vcard->details->font, + 14, + title, title_len); + + /* draw the name into the pixbuf using anti-aliased text */ + eel_scalable_font_draw_text (vcard->details->font, pixbuf, + 4, v_offset, + NULL, + 14, + title, title_len, + EEL_RGB_COLOR_BLACK, + EEL_OPACITY_FULLY_OPAQUE); + v_offset += title_dimensions.height + 4; + + g_free (title); + } + } + return v_offset; +} + +/* draw the addresses and phone numbers associated with the vcard */ +static int +draw_vcard_addresses (NautilusVCard *vcard, GdkPixbuf *pixbuf, int v_offset) +{ + return v_offset; +} + +/* handle drawing the control */ +static void +nautilus_vcard_draw (GtkWidget *widget, GdkRectangle *box) +{ + NautilusVCard *control; + GdkPixbuf *temp_pixbuf; + int width, height, v_offset; + + /* allocate a pixbuf to draw into */ + width = widget->allocation.width; + height = widget->allocation.height; + + + temp_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, width, height); + + g_return_if_fail (widget != NULL); + g_return_if_fail (NAUTILUS_IS_VCARD (widget)); + + control = NAUTILUS_VCARD (widget); + + /* draw the background */ + eel_gdk_pixbuf_fill_rectangle_with_color (temp_pixbuf, NULL, 0xFFEFEFEF); + + /* draw the logo, if any */ + v_offset = draw_vcard_logo_image (control, temp_pixbuf, 2); + + /* draw the name and title */ + v_offset = draw_vcard_name_and_title (control, temp_pixbuf, v_offset); + + v_offset += 6; + /* draw the addresses */ + v_offset = draw_vcard_addresses (control, temp_pixbuf, v_offset); + + /* blit the resultingpixbuf to the drawable, then release it */ + gdk_pixbuf_render_to_drawable_alpha (temp_pixbuf, + widget->window, + 0, 0, + widget->allocation.x, widget->allocation.y, + width, height, + GDK_PIXBUF_ALPHA_BILEVEL, 128, + GDK_RGB_DITHER_MAX, + 0, 0); + + gdk_pixbuf_unref (temp_pixbuf); +} + +/* handle expose events */ +static int +nautilus_vcard_expose (GtkWidget *widget, GdkEventExpose *event) +{ + GdkRectangle box; + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (NAUTILUS_IS_VCARD (widget), FALSE); + + box.x = 0; box.y = 0; + box.width = widget->allocation.width; + box.height = widget->allocation.height; + + nautilus_vcard_draw (widget, &box); + return FALSE; +} + + +/* handle mouse motion events by maintaining the prelight state */ +static gboolean +nautilus_vcard_motion_event (GtkWidget *widget, GdkEventMotion *event) +{ + int x, y; + int which_item, item_count; + NautilusVCard *vcard; + + vcard = NAUTILUS_VCARD (widget); + + gtk_widget_get_pointer (widget, &x, &y); + which_item = 0; + item_count = 0; + + if (which_item < 0 || which_item >= item_count) { + which_item = -1; + } + return TRUE; +} + +/* handle size requests by requesting a fixed size */ +static void +nautilus_vcard_size_request (GtkWidget *widget, GtkRequisition *request) +{ + request->width = 220; + request->height = 140; +} + +/* handle leave events by cancelling any prelighting */ +static gboolean +nautilus_vcard_leave_event (GtkWidget *widget, GdkEventCrossing *event) +{ + NautilusVCard *vcard; + + vcard = NAUTILUS_VCARD (widget); + return TRUE; +} + +/* handle button press events */ +static gboolean +nautilus_vcard_button_press_event (GtkWidget *widget, GdkEventButton *event) +{ + GList *selected_item; + NautilusVCard *vcard; + char *command; + int result, which_item; + + vcard = NAUTILUS_VCARD (widget); + if (event->y < widget->allocation.y) { + command = g_strdup_printf ("nautilus %s", vcard->details->vcard_uri); + result = system (command); + g_free (command); + } else { + which_item = (event->y - widget->allocation.y ) / 16; + if (which_item < 0) { + selected_item = 0; + + } + } + + return FALSE; +} + diff --git a/components/vcard/nautilus-vcard.h b/components/vcard/nautilus-vcard.h new file mode 100644 index 000000000..4a742dd26 --- /dev/null +++ b/components/vcard/nautilus-vcard.h @@ -0,0 +1,54 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* + * Copyright (C) 2000 Eazel, Inc + * + * 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: Andy Hertzfeld + */ + +/* header file for the vcard component */ + +#ifndef NAUTILUS_VCARD_H +#define NAUTILUS_VCARD_H + +#include <bonobo.h> +#include <gtk/gtkeventbox.h> +typedef struct _NautilusVCard NautilusVCard; +typedef struct _NautilusVCardClass NautilusVCardClass; + +#define NAUTILUS_TYPE_VCARD (nautilus_vcard_get_type ()) +#define NAUTILUS_VCARD(obj) (GTK_CHECK_CAST ((obj), NAUTILUS_TYPE_VCARD, NautilusVCard)) +#define NAUTILUS_VCARD_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), NAUTILUS_TYPE_VCARD, NautilusVCardClass)) +#define NAUTILUS_IS_VCARD(obj) (GTK_CHECK_TYPE ((obj), NAUTILUS_TYPE_VCARD)) +#define NAUTILUS_IS_VCARD_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_VCARD)) + +typedef struct _NautilusVCardDetails NautilusVCardDetails; + +struct _NautilusVCard { + GtkEventBox parent; + NautilusVCardDetails *details; +}; + +struct _NautilusVCardClass { + GtkEventBoxClass parent_class; +}; + +/* GtkObject support */ +GtkType nautilus_vcard_get_type (void); +BonoboObject* nautilus_vcard_get_control (NautilusVCard *vcard); + +#endif /* NAUTILUS_VCARD_H */ diff --git a/components/vcard/nautilus-vcard.oaf b/components/vcard/nautilus-vcard.oaf new file mode 100644 index 000000000..bb6edd1ea --- /dev/null +++ b/components/vcard/nautilus-vcard.oaf @@ -0,0 +1,20 @@ +<oaf_info> + +<oaf_server iid="OAFIID:nautilus_vcard_factory" type="exe" location="nautilus-rss-control"> +<oaf_attribute name="repo_ids" type="stringv"> +<item value="IDL:Bonobo/GenericFactory:1.0"/> +</oaf_attribute> +<oaf_attribute name="name" type="string" value="vcard factory"/> +<oaf_attribute name="description" type="string" value="vcard object factory"/> +</oaf_server> + +<oaf_server iid="OAFIID:nautilus_vcard" type="factory" location="OAFIID:nautilus_vcard_factory"> +<oaf_attribute name="repo_ids" type="stringv"> +<item value="IDL:Bonobo/Control:1.0"/> +<item value="IDL:Bonobo/Unknown:1.0"/> +</oaf_attribute> +<oaf_attribute name="name" type="string" value="vcard"/> +<oaf_attribute name="description" type="string" value="nautilus vcard object"/> +</oaf_server> + +</oaf_info> diff --git a/components/vcard/vcard.c b/components/vcard/vcard.c new file mode 100644 index 000000000..58000315e --- /dev/null +++ b/components/vcard/vcard.c @@ -0,0 +1,276 @@ +/* ad hoc vcard parsing routines for prototyping - replace with something better soon + * + * Copyright (C) 1999 Andy Hertzfeld + * + * 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 + */ + +#include <gnome.h> +#include <ctype.h> + +#include "vcard.h" + +char * vcard_full_name (gchar* vcard) + { + gchar full_name[2048]; + gchar *temp_str = strstr(vcard, "FN:"); + if (temp_str) + { + gint str_len; + gchar *end_pos = strchr(temp_str, '\n'); + if (!end_pos) + end_pos = temp_str + strlen(temp_str); + + str_len = end_pos - temp_str - 3; + memcpy(full_name, temp_str + 3, str_len); + full_name[str_len] = '\0'; + return g_strdup (full_name); + } + else + return NULL; + } + +char * vcard_title (gchar* vcard) + { + gchar title[2048]; + gchar *temp_str = strstr(vcard, "TITLE:"); + if (temp_str) + { + gint str_len; + gchar *end_pos = strchr(temp_str, '\n'); + if (!end_pos) + end_pos = temp_str + strlen(temp_str); + + str_len = end_pos - temp_str - 6; + memcpy(title, temp_str + 6, str_len); + title[str_len] = '\0'; + return g_strdup (title); + } + else + return NULL; + } + +char * vcard_logo (gchar* vcard) + { + gchar logo[2048]; + gchar *temp_str = strstr(vcard, "LOGO;VALUE=uri:"); + if (temp_str) + { + gint str_len; + gchar *end_pos = strchr(temp_str, '\n'); + if (!end_pos) + end_pos = temp_str + strlen(temp_str); + + str_len = end_pos - temp_str - 15; + memcpy(logo, temp_str + 15, str_len); + logo[str_len] = '\0'; + return g_strdup (logo); + } + else + return NULL; + } + +char * vcard_postal (gchar* vcard) + { + gchar postal[2048]; + gchar *temp_str = strstr(vcard, "ADR;"); + if (temp_str) + { + gchar *skip_pos = strchr(temp_str, ':'); + if (skip_pos) + { + gint str_len; + gchar *end_pos = strchr(skip_pos, '\n'); + if (!end_pos) + end_pos = skip_pos + strlen(skip_pos); + + str_len = end_pos - skip_pos - 1; + memcpy(postal, skip_pos + 1, str_len); + postal[str_len] = '\0'; + return g_strdup (postal); + } + else + return NULL; + } + else + return NULL; + } + +char * vcard_streetaddress(gchar* vcard) + { + gchar streetaddress[2048]; + gchar *temp_str = strstr(vcard, "ADR;"); + if (temp_str) + { + gchar *skip_pos = strchr(temp_str, ':'); + if (skip_pos) + { + gint str_len; + gchar *end_pos; + gint semi_count = 0; + + /* to extract the street address, count up two semi-colons, then scan until the next one */ + + while (*skip_pos && (semi_count < 2)) + { + if (*skip_pos++ == ';') + semi_count += 1; + } + + end_pos = strchr(skip_pos, ';'); + if (!end_pos) + end_pos = skip_pos + strlen(skip_pos); + + str_len = end_pos - skip_pos; + + memcpy(streetaddress, skip_pos, str_len); + streetaddress[str_len] = '\0'; + return g_strdup (streetaddress); + } + else + return NULL; + } + else + return NULL; + } + +char * vcard_city_state_zip(gchar* vcard) + { + gchar city[2048]; + gchar *temp_str = strstr(vcard, "ADR;"); + if (temp_str) + { + gchar *skip_pos = strchr(temp_str, ':'); + if (skip_pos) + { + gint str_len; + gchar *end_pos; + gint semi_count = 0; + + /* to extract the city, state and zip, count up 3 semi-colons */ + + while (*skip_pos && (semi_count < 3)) + { + if (*skip_pos++ == ';') + semi_count += 1; + } + + end_pos = skip_pos + strlen(skip_pos); + + str_len = end_pos - skip_pos; + memcpy(city, skip_pos, str_len); + city[str_len] = '\0'; + return g_strdup (city); + } + else + return NULL; + } + else + return NULL; + } + +char * vcard_company(gchar* vcard) + { + gchar company[2048]; + gchar *temp_str = strstr(vcard, "ORG:"); + if (temp_str) + { + gint str_len; + gchar *end_pos = strchr(temp_str, '\n'); + if (!end_pos) + end_pos = temp_str + strlen(temp_str); + + str_len = end_pos - temp_str - 4; + memcpy(company, temp_str + 4, str_len); + company[str_len] = '\0'; + return g_strdup (company); + } + else + return NULL; + } + +char * vcard_email(gchar* vcard) + { + return NULL; + } + +char * vcard_fax(gchar* vcard) + { + return NULL; + } + +char * vcard_web(gchar* vcard) + { + return NULL; + } + +/* this routine returns a cr-delimited list of all the phone, email and web addresses it can find */ + +char * vcard_address_list(gchar* vcard) + { + gchar *next_line, *scan_ptr; + gchar temp_card[16384]; + + strcpy(temp_card, vcard); + + /* iterate through the vcard text, extracting the fields that we're interested in and building the returned list */ + + next_line = strtok(temp_card, "\n"); + while (next_line != NULL) + { + /* skip over leading blanks, and find the tag delimiter */ + + next_line = g_strchug(next_line); + scan_ptr = next_line; + + while (isalpha (*scan_ptr)) + scan_ptr++; + + if ((*scan_ptr == ';') || (*scan_ptr == ':')) + { + gchar delimiter = *scan_ptr; + *scan_ptr = '\0'; + + /* see if it's one of the tags that we collect */ + + if (!strcmp(next_line, "TEL") || !strcmp(next_line, "EMAIL") || !strcmp(next_line, "URL")) + { + gchar temp_address[2048]; + + /* it matched, s prepare the output string and append it to the result */ + + if (delimiter == ':') + sprintf(temp_address, "%s||%s\n", next_line, scan_ptr + 1); + else + { + gchar *sub_type_ptr; + + scan_ptr += 1; + sub_type_ptr = scan_ptr; + while ((*scan_ptr != ':') && (*scan_ptr != '\0')) + scan_ptr += 1; + *scan_ptr = '\0'; + sprintf(temp_address, "%s|%s|%s\n", next_line, sub_type_ptr, scan_ptr + 1); + } + /* + strcat(address_list, temp_address); + */ + } + } + next_line = strtok(NULL, "\n"); + } + + return NULL; + } diff --git a/components/vcard/vcard.h b/components/vcard/vcard.h new file mode 100644 index 000000000..0eeaff8b2 --- /dev/null +++ b/components/vcard/vcard.h @@ -0,0 +1,15 @@ + +/* ad hoc vcard parsing routines - replace with something better sometime */ + +char * vcard_full_name (gchar* vcard); +char * vcard_title(gchar* vcard); +char * vcard_postal(gchar* vcard); +char * vcard_email(gchar* vcard); +char * vcard_fax(gchar* vcard); +char * vcard_web(gchar* vcard); +char * vcard_logo(gchar* vcard); +char * vcard_streetaddress(gchar* vcard); +char * vcard_city_state_zip(gchar* vcard); +char * vcard_company(gchar* vcard); +char * vcard_address_list(gchar* vcard); + diff --git a/configure.in b/configure.in index 1c7e7adcc..fbd03a01f 100644 --- a/configure.in +++ b/configure.in @@ -937,6 +937,7 @@ components/music/Makefile components/notes/Makefile components/sample/Makefile components/mozilla/Makefile +components/rss-control/Makefile components/text/Makefile components/text/services/Makefile components/throbber/Makefile @@ -944,6 +945,7 @@ components/loser/Makefile components/loser/content/Makefile components/loser/sidebar/Makefile components/tree/Makefile +components/vcard/Makefile po/Makefile.in intl/Makefile test/Makefile diff --git a/icons/Makefile.am b/icons/Makefile.am index 8b35d7f43..c7075d8a2 100644 --- a/icons/Makefile.am +++ b/icons/Makefile.am @@ -22,6 +22,7 @@ icon_DATA =\ bubble-LR.png \ bubble-UL.png \ bubble-UR.png \ + bullet.png \ chit_frame.png \ colors.png \ computer.png \ @@ -43,6 +44,7 @@ icon_DATA =\ emblem-important.svg \ emblem-new.svg \ emblem-noread.svg \ + emblem-note.png \ emblem-nowrite.svg \ emblem-ohno.svg \ emblem-personal.svg \ diff --git a/icons/emblem-note.png b/icons/emblem-note.png Binary files differnew file mode 100644 index 000000000..54681aea6 --- /dev/null +++ b/icons/emblem-note.png diff --git a/libnautilus-extensions/Makefile.am b/libnautilus-extensions/Makefile.am index c0094f54d..515fdce88 100644 --- a/libnautilus-extensions/Makefile.am +++ b/libnautilus-extensions/Makefile.am @@ -38,6 +38,7 @@ endif libnautilus_extensions_la_LDFLAGS = \ $(dependency_static_libs) \ + $(AMMONITE_LIBS) \ $(BONOBO_PRINT_LIBS) \ $(BONOBOX_LIBS) \ $(ESD_LIBS) \ @@ -63,9 +64,11 @@ nautilus_metafile_server_idl_sources = \ libnautilus_extensions_la_SOURCES = \ $(nautilus_metafile_server_idl_sources) \ + nautilus-annotation.c \ nautilus-audio-player.c \ nautilus-bonobo-extensions.c \ nautilus-bookmark.c \ + nautilus-canvas-note-item.c \ nautilus-customization-data.c \ nautilus-dateedit-extensions.c \ nautilus-default-file-icon.c \ @@ -130,9 +133,11 @@ libnautilus_extensions_la_SOURCES = \ # Everything is private for now noinst_HEADERS = \ + nautilus-annotation.h \ nautilus-audio-player.h \ nautilus-bonobo-extensions.h \ nautilus-bookmark.h \ + nautilus-canvas-note-item.h \ nautilus-cdrom-extensions.h \ nautilus-customization-data.h \ nautilus-dateedit-extensions.h \ diff --git a/libnautilus-extensions/nautilus-annotation.c b/libnautilus-extensions/nautilus-annotation.c new file mode 100644 index 000000000..07a2d8512 --- /dev/null +++ b/libnautilus-extensions/nautilus-annotation.c @@ -0,0 +1,1312 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- + + nautilus-annotation.c: routines for getting and setting xml-based annotations associated + with the digest of a file. + + Copyright (C) 2001 Eazel, Inc. + + 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: Andy Hertzfeld <andy@eazel.com> +*/ + +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to md5_init, call md5_update as + * needed on buffers full of bytes, and then call md5_Final, which + * will fill a supplied 16-byte array with the digest. + */ + +/* parts of this file are : + * Written March 1993 by Branko Lankester + * Modified June 1993 by Colin Plumb for altered md5.c. + * Modified October 1995 by Erik Troan for RPM + */ + +#include <config.h> +#include "nautilus-annotation.h" + +#include "nautilus-file-utilities.h" +#include "nautilus-file.h" +#include "nautilus-file-private.h" +#include "nautilus-global-preferences.h" +#include "nautilus-metadata.h" +#include "nautilus-preferences.h" +#include <eel/eel-string.h> +#include <eel/eel-xml-extensions.h> +#include <eel/eel-vfs-extensions.h> +#include <ghttp.h> +#include <gnome-xml/parser.h> +#include <gnome-xml/xmlmemory.h> +#include <libgnome/gnome-util.h> +#include <libgnomevfs/gnome-vfs.h> +#include <libtrilobite/libammonite.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* icon selection callback function. */ +typedef void (* NautilusCalculateDigestCallback) (NautilusFile *file, char *file_digest); +typedef struct NautilusDigestFileHandle NautilusDigestFileHandle; + +typedef struct { + guint32 buf[4]; + guint32 bits[2]; + guchar in[64]; + int doByteReverse; +} MD5Context ; + +struct NautilusDigestFileHandle { + GnomeVFSAsyncHandle *handle; + NautilusCalculateDigestCallback callback; + NautilusFile *file; + char *buffer; + gboolean opened; + MD5Context digest_context; +}; + +#define READ_CHUNK_SIZE 65536 +#define MAX_DIGESTS_IN_PROGRESS 16 +#define SERVER_URI_TEMPLATE "http://dellbert.differnet.com/get_notes.cgi?ids=%s" +#define SERVER_POST_URI "http://dellbert.differnet.com/set_notes.cgi" +#define NOTES_LOOKUP_INTERVAL 3600 + +static int open_count = 0; +static int close_count = 0; +static int digests_in_progress = 0; + +static GList* digest_request_queue = NULL; +static GList* annotation_request_queue = NULL; + +static GHashTable *files_awaiting_annotation = NULL; + +static void md5_transform (guint32 buf[4], const guint32 in[16]); + +static int _ie = 0x44332211; +static union _endian { int i; char b[4]; } *_endian = (union _endian *)&_ie; +#define IS_BIG_ENDIAN() (_endian->b[0] == '\x44') +#define IS_LITTLE_ENDIAN() (_endian->b[0] == '\x11') + +static void got_file_digest (NautilusFile *file, const char *file_digest); +static void process_digest_requests (void); + +/* + * Note: this code is harmless on little-endian machines. + */ +static void +_byte_reverse (guchar *buf, guint32 longs) +{ + guint32 t; + do { + t = (guint32) ((guint32) buf[3] << 8 | buf[2]) << 16 | + ((guint32) buf[1] << 8 | buf[0]); + *(guint32 *) buf = t; + buf += 4; + } while (--longs); +} + +/** + * md5_init: Initialise an md5 context object + * @ctx: md5 context + * + * Initialise an md5 buffer. + * + **/ +static void +md5_init (MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; + + if (IS_BIG_ENDIAN()) + ctx->doByteReverse = 1; + else + ctx->doByteReverse = 0; +} + + + +/** + * md5_update: add a buffer to md5 hash computation + * @ctx: conetxt object used for md5 computaion + * @buf: buffer to add + * @len: buffer length + * + * Update context to reflect the concatenation of another buffer full + * of bytes. Use this to progressively construct an md5 hash. + **/ +static void +md5_update (MD5Context *ctx, const guchar *buf, guint32 len) +{ + guint32 t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((guint32) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + guchar *p = (guchar *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy (p, buf, len); + return; + } + memcpy (p, buf, t); + if (ctx->doByteReverse) + _byte_reverse (ctx->in, 16); + md5_transform (ctx->buf, (guint32 *) ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy (ctx->in, buf, 64); + if (ctx->doByteReverse) + _byte_reverse (ctx->in, 16); + md5_transform (ctx->buf, (guint32 *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy (ctx->in, buf, len); +} + + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +/** + * md5_final: copy the final md5 hash to a bufer + * @digest: 16 bytes buffer + * @ctx: context containing the calculated md5 + * + * copy the final md5 hash to a bufer + **/ +static void +md5_final (MD5Context *ctx, guchar digest[16]) +{ + guint32 count; + guchar *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset (p, 0, count); + if (ctx->doByteReverse) + _byte_reverse (ctx->in, 16); + md5_transform (ctx->buf, (guint32 *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset (ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset (p, 0, count - 8); + } + if (ctx->doByteReverse) + _byte_reverse (ctx->in, 14); + + /* Append length in bits and transform */ + ((guint32 *) ctx->in)[14] = ctx->bits[0]; + ((guint32 *) ctx->in)[15] = ctx->bits[1]; + + md5_transform (ctx->buf, (guint32 *) ctx->in); + if (ctx->doByteReverse) + _byte_reverse ((guchar *) ctx->buf, 4); + memcpy (digest, ctx->buf, 16); +} + + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. md5_Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void +md5_transform (guint32 buf[4], const guint32 in[16]) +{ + register guint32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP (F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP (F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP (F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP (F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP (F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP (F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP (F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP (F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP (F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP (F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP (F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP (F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP (F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP (F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP (F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP (F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP (F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP (F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP (F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP (F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP (F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP (F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP (F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP (F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP (F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP (F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP (F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP (F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP (F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP (F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP (F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP (F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP (F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP (F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP (F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP (F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP (F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP (F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP (F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP (F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP (F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP (F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP (F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP (F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP (F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP (F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP (F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP (F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP (F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP (F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP (F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP (F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP (F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP (F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP (F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP (F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP (F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP (F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP (F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP (F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP (F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP (F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP (F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP (F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + + + +/* When close is complete, there's no more work to do. */ +static void +digest_file_close_callback (GnomeVFSAsyncHandle *handle, + GnomeVFSResult result, + gpointer callback_data) +{ + close_count += 1; + g_message ("opened %d, closed %d", open_count, close_count); +} + +/* Close the file and then tell the caller we succeeded, handing off + * the buffer to the caller. + */ +static void +digest_file_completed (NautilusDigestFileHandle *digest_handle) +{ + guchar digest_result[16]; + char digest_string [33]; + char* hex_string = "0123456789abcdef"; + int index, result_index; + int current_value; + + if (digest_handle->opened) { + + gnome_vfs_async_close (digest_handle->handle, + digest_file_close_callback, + NULL); + } + + digests_in_progress -= 1; + + /* Invoke the callback to continue processing the annotation */ + md5_final (&digest_handle->digest_context, digest_result); + + /* make a hex string for the digest result */ + digest_string[32] = '\0'; + for (index = 0; index < 32; index++) { + current_value = digest_result[index >> 1]; + if (index & 1) { + result_index = current_value & 15; + } else { + result_index = (current_value >> 4) & 15; + } + + digest_string[index] = hex_string[result_index]; + } + + (* digest_handle->callback) (digest_handle->file, &digest_string[0]); + + nautilus_file_unref (digest_handle->file); + g_free (digest_handle->buffer); + g_free (digest_handle); + + /* start new digest requests if necessary */ + process_digest_requests (); + +} + +/* Tell the caller we failed. */ +static void +digest_file_failed (NautilusDigestFileHandle *digest_handle, GnomeVFSResult result) +{ + if (digest_handle->opened) { + gnome_vfs_async_close (digest_handle->handle, + digest_file_close_callback, + NULL); + } + g_free (digest_handle->buffer); + + digests_in_progress -= 1; + + (* digest_handle->callback) (digest_handle->file, NULL); + + nautilus_file_unref (digest_handle->file); + g_free (digest_handle); + + /* start new digest requests if necessary */ + process_digest_requests (); +} + +/* Here is the callback from the file read routine, where we actually accumulate the checksum */ +static void +calculate_checksum_callback (GnomeVFSAsyncHandle *handle, + GnomeVFSResult result, + gpointer buffer, + GnomeVFSFileSize bytes_requested, + GnomeVFSFileSize bytes_read, + gpointer callback_data) +{ + NautilusDigestFileHandle *digest_handle; + + /* Do a few reality checks. */ + g_assert (bytes_requested == READ_CHUNK_SIZE); + + digest_handle = callback_data; + g_assert (digest_handle->handle == handle); + g_assert (bytes_read <= bytes_requested); + + /* Check for a failure. */ + if (result != GNOME_VFS_OK && result != GNOME_VFS_ERROR_EOF) { + digest_file_failed (digest_handle, result); + return; + } + + /* accumulate the recently read data into the checksum */ + if (bytes_read > 0) { + md5_update (&digest_handle->digest_context, buffer, bytes_read); + } + + /* Read more unless we are at the end of the file. */ + if (bytes_read > 0 && result == GNOME_VFS_OK) { + gnome_vfs_async_read (digest_handle->handle, + digest_handle->buffer, + READ_CHUNK_SIZE, + calculate_checksum_callback, + digest_handle); + } else { + digest_file_completed (digest_handle); + } +} + +/* Once the open is finished, read a first chunk. */ +static void +read_file_open_callback (GnomeVFSAsyncHandle *handle, + GnomeVFSResult result, + gpointer callback_data) +{ + NautilusDigestFileHandle *digest_handle; + char *name; + + digest_handle = callback_data; + g_assert (digest_handle->handle == handle); + + /* Handle the failure case. */ + if (result != GNOME_VFS_OK) { + name = nautilus_file_get_name (digest_handle->file); + g_message ("open failed, filename %s, error was %d", name, result); + g_free (name); + digest_file_failed (digest_handle, result); + return; + } + + /* read in the first chunk of the file */ + digest_handle->opened = TRUE; + open_count += 1; + gnome_vfs_async_read (digest_handle->handle, + digest_handle->buffer, + READ_CHUNK_SIZE, + calculate_checksum_callback, + digest_handle); +} + +/* calculate the digest for the passed-in file asynchronously, invoking the passed in + * callback when the calculation has been completed. + */ +static NautilusDigestFileHandle* +calculate_file_digest (NautilusFile *file, NautilusCalculateDigestCallback callback) +{ + NautilusDigestFileHandle *handle; + char *uri; + + + /* allocate a digest-handle structure to keep our state */ + + handle = g_new0 (NautilusDigestFileHandle, 1); + uri = nautilus_file_get_uri (file); + + handle->callback = callback; + handle->opened = FALSE; + handle->file = file; + nautilus_file_ref (file); + + /* allocate the buffer */ + handle->buffer = g_malloc (READ_CHUNK_SIZE); + + /* initialize the MD5 stuff */ + md5_init (&handle->digest_context); + + /* open the file */ + gnome_vfs_async_open (&handle->handle, + uri, + GNOME_VFS_OPEN_READ, + read_file_open_callback, + handle); + g_free (uri); + return handle; +} + +/* process the digest request queue, launching as many requests as we can handle */ +static void +process_digest_requests (void) +{ + GList *current_entry; + NautilusFile *file; + + while (digests_in_progress < MAX_DIGESTS_IN_PROGRESS && digest_request_queue != NULL) + { + /* pull entry off queue */ + current_entry = digest_request_queue; + digest_request_queue = current_entry->next; + + file = NAUTILUS_FILE (current_entry->data); + + /* initiate request */ + calculate_file_digest (file, (NautilusCalculateDigestCallback) got_file_digest); + + /* dispose of queue entry */ + nautilus_file_unref (file); + + g_list_free_1 (current_entry); + digests_in_progress += 1; + } +} + +/* queue the digest request, and start processing it if we haven't exceeded the limit of requests + * in progress + */ +static void +queue_file_digest_request (NautilusFile *file) +{ + /* if annotation lookup is disabled, don't bother to do all this work */ + if (!nautilus_preferences_get_boolean (NAUTILUS_PREFERENCES_LOOKUP_ANNOTATIONS)) { + return; + } + nautilus_file_ref (file); + digest_request_queue = g_list_append (digest_request_queue, file); + process_digest_requests (); +} + +/* given a digest, retrieve an associated file object from the hash table */ +static NautilusFile * +get_file_from_digest (const char *digest) +{ + if (files_awaiting_annotation == NULL) { + return NULL; + } + + return g_hash_table_lookup (files_awaiting_annotation, digest); +} + +/* given a digest value, return the path to it in the local cache */ +static char * +get_annotation_path (const char *digest) +{ + char *user_directory, *annotation_directory; + char *annotation_path, *directory_uri; + + user_directory = nautilus_get_user_directory (); + annotation_directory = nautilus_make_path (user_directory, "annotations"); + annotation_path = nautilus_make_path (annotation_directory, digest); + + /* create the annotation directory if it doesn't exist */ + if (!g_file_exists (annotation_directory)) { + directory_uri = gnome_vfs_get_uri_from_local_path (annotation_directory); + gnome_vfs_make_directory (directory_uri, + GNOME_VFS_PERM_USER_ALL + | GNOME_VFS_PERM_GROUP_ALL + | GNOME_VFS_PERM_OTHER_READ); + g_free (directory_uri); + } + + /* free up the intermediate strings and return the complete path */ + g_free (user_directory); + g_free (annotation_directory); + + return annotation_path; +} + +/* look up the passed-in digest in the local annotation cache */ +static char * +look_up_local_annotation (NautilusFile *file, const char *digest) +{ + GnomeVFSResult result; + int file_size; + char *uri, *path, *file_data, *buffer; + + path = get_annotation_path (digest); + if (g_file_exists (path)) { + /* load the file and return it */ + uri = gnome_vfs_get_uri_from_local_path (path); + result = eel_read_entire_file (uri, &file_size, &file_data); + g_free (uri); + g_free (path); + if (result == GNOME_VFS_OK) { + /* add a null at the end, so it's a valid string */ + buffer = g_realloc (file_data, file_size + 1); + buffer[file_size] = '\0'; + return buffer; + } else { + return NULL; + } + } + g_free (path); + return NULL; +} + +static gboolean +has_local_annotation (const char *digest) +{ + gboolean has_annotation; + char *path; + + path = get_annotation_path (digest); + has_annotation = g_file_exists (path); + + g_free (path); + return has_annotation; +} + +/* utility routine to save the passed-in xml document as a local annotations file */ +static void +save_local_annotations (xmlDocPtr document, const char *digest) +{ + char *path; + + path = get_annotation_path (digest); + xmlSaveFile (path, document); + + g_free (path); +} + +/* utility routine to add the passed-in xml node to the file associated with the passed-in + * digest. If there isn't a file, create one + */ +static void +add_annotations_to_file (xmlNodePtr node_ptr, const char *digest) +{ + char *digest_path; + xmlDocPtr document; + + digest_path = get_annotation_path (digest); + + /* save the subtree as a new document, by making a new document and adding the new node */ + document = xmlNewDoc ("1.0"); + xmlDocSetRootElement (document, node_ptr); + + /* save the xml tree as a file in the cache area */ + xmlSaveFile (digest_path, document); + + xmlFreeDoc (document); + g_free (digest_path); +} + +/* remember the file object by adding it to a hash table */ +static void +remember_file (NautilusFile *file, const char *digest) +{ + nautilus_file_ref (file); + + if (files_awaiting_annotation == NULL) { + files_awaiting_annotation = g_hash_table_new (g_str_hash, g_str_equal); + /* g_atexit (annotations_file_table_free); */ + } + + g_hash_table_insert (files_awaiting_annotation, g_strdup (digest), file); +} + +/* forget a file when we're done with it by removing it from the table */ +static void +forget_file (const char *digest) +{ + NautilusFile *file; + if (files_awaiting_annotation == NULL) { + return; + } + + file = g_hash_table_lookup (files_awaiting_annotation, digest); + if (file != NULL) { + nautilus_file_unref (file); + g_hash_table_remove (files_awaiting_annotation, digest); + } +} + +/* completion routine invoked when we've loaded the an annotation file from the service. + * We must parse it, and walk through it to save the annotations in the local cache. + */ +static void +got_annotations_callback (GnomeVFSResult result, + GnomeVFSFileSize file_size, + char *file_contents, + gpointer callback_data) +{ + NautilusFile *file; + xmlDocPtr annotations; + xmlNodePtr next_annotation, item; + xmlNodePtr saved_annotation; + int annotation_count; + char *buffer, *digest, *info_str; + time_t date_stamp; + + /* exit if there was an error */ + if (result != GNOME_VFS_OK) { + g_assert (file_contents == NULL); + return; + } + + /* inexplicably, the gnome-xml parser requires a zero-terminated array, so add the null at the end. */ + buffer = g_realloc (file_contents, file_size + 1); + buffer[file_size] = '\0'; + annotations = xmlParseMemory (buffer, file_size); + g_free (buffer); + + /* iterate through the xml document, handling each annotation entry */ + if (annotations != NULL) { + next_annotation = xmlDocGetRootElement (annotations)->childs; + while (next_annotation != NULL) { + if (eel_strcmp (next_annotation->name, "annotations") == 0) { + /* get the digest associated with the annotations */ + digest = xmlGetProp (next_annotation, "digest"); + if (digest != NULL) { + /* count the number of annotations contained in the node */ + annotation_count = 0; + item = next_annotation->childs; + while (item != NULL) { + if (eel_strcmp (item->name, "annotation") == 0) { + annotation_count += 1; + } + item = item->next; + } + + /* write the annotation out to our cache area, if necessary */ + if (annotation_count > 0) { + saved_annotation = xmlCopyNode (next_annotation, TRUE); + add_annotations_to_file (saved_annotation, digest); + } + + /* retrieve the file object, and update it's count and time stamp */ + + file = get_file_from_digest (digest); + time (&date_stamp); + info_str = g_strdup_printf ("%lu:%d", date_stamp, annotation_count); + + nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_NOTES_INFO, NULL, info_str); + g_free (info_str); + + /* issue the changed signal */ + nautilus_file_emit_changed (file); + + /* remove the file from the hash table and unref it */ + forget_file (digest); + xmlFree (digest); + } + } + next_annotation = next_annotation->next; + } + + + /* free the xml document */ + xmlFreeDoc (annotations); + } +} + +/* format the request, and send it to the server */ +/* the first cut implementation simply sends the digests as a cgi parameter, + * but soon we'll want use SOAP or XML-RPC + */ +static void +fetch_annotations_from_server (void) +{ + GString *temp_string; + GList *current_entry, *save_entry; + char *uri; + + /* check to see if there are enough requests, or a long enough delay since the last one */ + + current_entry = annotation_request_queue; + save_entry = current_entry; + annotation_request_queue = NULL; + + /* simple cgi-based request format passed the digests as part of the uri, so + * gather the variable parts + */ + temp_string = g_string_new (""); + while (current_entry != NULL) { + g_string_append (temp_string, (char*) current_entry->data); + if (current_entry->next != NULL) { + g_string_append (temp_string, ","); + } + current_entry = current_entry->next; + } + + + uri = g_strdup_printf (SERVER_URI_TEMPLATE, temp_string->str); + g_string_free (temp_string, TRUE); + eel_g_list_free_deep (save_entry); + + /* read the result from the server asynchronously */ + eel_read_entire_file_async (uri, got_annotations_callback, NULL); + g_free (uri); +} + + +/* ask the server for an annotation asynchronously */ +static void +get_annotation_from_server (NautilusFile *file, const char *file_digest) +{ + /* see if there's a request for this one already pending - if so, we can return */ + if (get_file_from_digest (file_digest) != NULL) { + return; + } + + /* only do this if lookups are enabled */ + /* if annotation lookup is disabled, don't bother to do all this work */ + if (!nautilus_preferences_get_boolean (NAUTILUS_PREFERENCES_LOOKUP_ANNOTATIONS)) { + return; + } + + /* add the request to the queue, and kick it off it there's enough of them */ + annotation_request_queue = g_list_prepend (annotation_request_queue, g_strdup (file_digest)); + + remember_file (file, file_digest); + fetch_annotations_from_server (); +} + +/* callback that's invokes when we've finished calculating the file's digest. Remember + * it in the metadata, and look up the associated annotation + */ +static void +got_file_digest (NautilusFile *file, const char *file_digest) +{ + + if (file_digest == NULL) { + return; + } + + /* save the digest in the file metadata */ + nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_FILE_DIGEST, NULL, file_digest); + + /* lookup the annotations associated with the file. If there is one, flag the change and we're done */ + if (has_local_annotation (file_digest)) { + nautilus_file_emit_changed (file); + return; + } + + /* there isn't a local annotation, so ask the server for one */ + get_annotation_from_server (file, file_digest); + return; +} + +/* utility routine that takes a passed-in notes-info string and returns true if the + * encoded date is old enough to require a new look-up + */ +static gboolean +annotation_is_stale (const char *notes_info) +{ + time_t info_date, date_stamp; + + if (notes_info == NULL) { + return TRUE; + } + + info_date = strtoul (notes_info, NULL, 10); + time (&date_stamp); + + /* eventually, the lookup interval should be a preference, not a constant */ + return (date_stamp - info_date) > NOTES_LOOKUP_INTERVAL; +} + +/* return the annotation associated with a file. If we haven't inspected this file yet, + * return NULL but queue a request for an annotation lookup, which will be processed + * asynchronously and issue a "file_changed" signal if any is found. + */ +char *nautilus_annotation_get_annotation (NautilusFile *file) +{ + char *digest; + char *annotations; + char *digest_info; + + /* if it's a directory, return NULL, at least until we figure out how to handle directory + * annotations + */ + if (nautilus_file_is_directory (file)) { + return NULL; + } + + /* see if there's a digest available in metadata */ + digest = nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_FILE_DIGEST, NULL); + + /* there isn't a digest, so start a request for one going, and return NULL */ + if (digest == NULL) { + queue_file_digest_request (file); + return NULL; + } + + /* if we haven't update the info in a while, initiate a lookup */ + digest_info = nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_NOTES_INFO, NULL); + if (annotation_is_stale (digest_info)) { + get_annotation_from_server (file, digest); + } + g_free (digest_info); + + /* there's a digest, so we if we have the annotations for the file cached locally */ + annotations = look_up_local_annotation (file, digest); + if (annotations != NULL) { + g_free (digest); + return annotations; + } + + g_free (digest); + return NULL; +} + +/* utility routine to map raw annotation text into text to be displayed + * for now this is pretty naive and only handles free-form text, just returning + * the first suitable annotation it can find. + */ +char * +nautilus_annotation_get_display_text (const char *note_text) +{ + char *display_text, *temp_text; + xmlChar *xml_text; + xmlDocPtr annotations; + xmlNodePtr next_annotation; + + /* if its an xml file, parse it to extract the display text */ + if (eel_istr_has_prefix (note_text, "<?xml")) { + display_text = NULL; + annotations = xmlParseMemory ((char*) note_text, strlen (note_text)); + if (annotations != NULL) { + next_annotation = xmlDocGetRootElement (annotations)->childs; + while (next_annotation != NULL) { + if (eel_strcmp (next_annotation->name, "annotation") == 0) { + xml_text = xmlNodeGetContent (next_annotation); + temp_text = (char*) xml_text; + while (*temp_text && *temp_text < ' ') temp_text++; + display_text = g_strdup (temp_text); + xmlFree (xml_text); + break; + } + next_annotation = next_annotation->next; + } + xmlFreeDoc (annotations); + } + } else { + display_text = g_strdup (note_text); + } + return display_text; +} + +/* convenience routine to return the display text of an annotation associated + * with a file + */ +char * +nautilus_annotation_get_annotation_for_display (NautilusFile *file) +{ + char *raw_text, *display_text; + + raw_text = nautilus_annotation_get_annotation (file); + if (raw_text != NULL) { + display_text = nautilus_annotation_get_display_text (raw_text); + g_free (raw_text); + return display_text; + } + return NULL; +} + +/* return the number of annotations associated with the passed in file. If we don't know, + * return 0, but queue a request like above + */ +int nautilus_annotation_has_annotation (NautilusFile *file) +{ + char *digest_info, *digits, *temp_str; + int count = 0; + + if (!nautilus_preferences_get_boolean (NAUTILUS_PREFERENCES_DISPLAY_ANNOTATIONS)) { + return 0; + } + + digest_info = nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_NOTES_INFO, NULL); + + if (digest_info != NULL) { + digits = strrchr (digest_info, ':'); + count = atoi (digits + 1); + g_free (digest_info); + + /* if the date is stale, launch a new lookup */ + if (annotation_is_stale (digest_info)) { + temp_str = nautilus_annotation_get_annotation (file); + g_free (temp_str); + } + return count; + } else { + /* initiate fetching the annotations from the server */ + temp_str = nautilus_annotation_get_annotation (file); + g_free (temp_str); + } + g_free (digest_info); + return 0; +} + +/* utility routine for making an HTTP POST request with ghttp */ +static gboolean +http_post_simple (const char *uri, const char *name, const char *value) { + ghttp_request* request; + char *ename=NULL, *evalue=NULL, *body=NULL; + + request = ghttp_request_new (); + if (!request) { + return FALSE; + } + + if (ghttp_set_uri (request, (char *)uri) != 0 || + ghttp_set_type (request, ghttp_type_post) != 0) { + ghttp_close (request); + return FALSE; + } + ghttp_set_header (request, http_hdr_Connection, "close"); + ghttp_set_header (request, http_hdr_User_Agent, "Nautilus Annotation"); + ghttp_set_header (request, http_hdr_Content_Type, "application/x-www-form-urlencoded"); + + evalue = gnome_vfs_escape_string (value); + if (name) { + ename = gnome_vfs_escape_string (name); + body = g_strconcat (ename, "=", evalue, NULL); + g_free (ename); + g_free (evalue); + } else { + body = evalue; + } + + if (ghttp_set_body (request, body, strlen(body)) != 0 || + ghttp_prepare (request) != 0) { + ghttp_close (request); + return FALSE; + } + + if (ghttp_process (request) != ghttp_done) { + ghttp_close (request); + return FALSE; + } + + ghttp_close (request); + return TRUE; +} + +/* utility to count the number of annotations in the passed-in xml document */ +static int +count_annotations (xmlDocPtr xml_document) +{ + xmlNodePtr next_annotation; + int annotation_count; + + annotation_count = 0; + next_annotation = xmlDocGetRootElement (xml_document)->childs; + + while (next_annotation != NULL) { + if (eel_strcmp (next_annotation->name, "annotation") == 0) { + annotation_count += 1; + } + next_annotation = next_annotation->next; + } + return annotation_count; +} + +/* + * get_ammonite_get_default_user_username + * + * Returns username of the currently logged-in default Eazel Service User + * or NULL if there isn't one + */ + + +/* send the local annotations associated with the passed-in digest to the server */ +static void +nautilus_annotation_send_to_server (const char *digest, + const char *annotation_type, + const char *annotation_text, + const char *date_str) +{ + char *user_id; + xmlChar *request_text; + xmlDocPtr xml_document; + xmlNodePtr root_node, annotation_node; + int request_size; + + /* get the user name */ + user_id = ammonite_get_default_user_username (); + + /* if the user wasn't logged in, prompt for it (coming soon, for now, just use anonymous */ + if (user_id == NULL) { + user_id = g_strdup ("anonymous"); + } + + /* create an xml document to hold the annotation posting */ + xml_document = xmlNewDoc ("1.0"); + + /* create the header node, with the digest attribute */ + root_node = xmlNewDocNode (xml_document, NULL, "annotations", NULL); + xmlDocSetRootElement (xml_document, root_node); + xmlSetProp (root_node, "digest", digest); + xmlSetProp (root_node, "user", user_id); + + /* set up the annotation payload */ + annotation_node = xmlNewChild (root_node, NULL, "annotation", NULL); + xmlSetProp (annotation_node, "type", annotation_type); + xmlSetProp (annotation_node, "date", date_str); + + xmlNodeSetContent (annotation_node, annotation_text); + + /* post the annotation request to the server using ghttp */ + xmlDocDumpMemory (xml_document, &request_text, &request_size); + + if (!http_post_simple (SERVER_POST_URI, "note", request_text)) { + g_message ("post request failed"); + } + + /* clean up and we're done */ + xmlFree (request_text); + g_free (user_id); + xmlFreeDoc (xml_document); + +} + +/* add an annotation to a file */ +void +nautilus_annotation_add_annotation (NautilusFile *file, + const char *annotation_type, + const char *annotation_text, + const char *access) +{ + char *digest; + char *annotations; + char *info_str, *date_str; + char *annotation_path; + int annotation_count; + time_t date_stamp; + xmlDocPtr xml_document; + xmlNodePtr root_node, node; + gboolean has_annotation, has_new_annotation; + + /* we can't handle directories yet, so just return. */ + if (nautilus_file_is_directory (file)) { + return; + } + + has_new_annotation = annotation_text != NULL && strlen (annotation_text) > 0; + + /* fetch the local annotation, if one exists */ + digest = nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_FILE_DIGEST, NULL); + + /* there isn't a digest, so start a request for one going, and return */ + /* this shouldn't happen in practice, since the annotation window will have + * already created a digest + */ + if (digest == NULL) { + queue_file_digest_request (file); + return; + } + + /* there's a digest, so we if we have the annotations for the file cached locally */ + annotations = look_up_local_annotation (file, digest); + has_annotation = annotations != NULL && strlen (annotations) > 0; + + /* handle the case of no annotation, by removing it if necessary */ + if (!has_new_annotation) { + if (has_annotation) { + /* delete the annotation */ + annotation_path = get_annotation_path (digest); + unlink (annotation_path); + + /* set the info to indicate no notes available */ + time (&date_stamp); + info_str = g_strdup_printf ("%lu:%d", date_stamp, 0); + nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_NOTES_INFO, NULL, info_str); + g_free (info_str); + + nautilus_file_emit_changed (file); + + g_free (annotation_path); + } + + g_free (digest); + g_free (annotations); + return; + } + + /* there is a new annotation, but no current annotation exists, so create the + * initial xml document from scratch + */ + if (!has_annotation) { + xml_document = xmlNewDoc ("1.0"); + /* create the header node, with the digest attribute */ + root_node = xmlNewDocNode (xml_document, NULL, "annotations", NULL); + xmlDocSetRootElement (xml_document, root_node); + xmlSetProp (root_node, "digest", digest); + } else { + /* open the existing annotation and load it */ + xml_document = xmlParseMemory (annotations, strlen (annotations)); + root_node = xmlDocGetRootElement (xml_document); + } + + time (&date_stamp); + date_str = g_strdup_printf ("%lu", date_stamp); + + /* add the new entry. For now, we only support one entry per file, so we replace the old + * one, if it exists, but this will change soon as we support multiple notes per file + */ + if (root_node->childs == NULL) { + node = xmlNewChild (root_node, NULL, "annotation", NULL); + xmlSetProp (node, "type", annotation_type); + + date_str = g_strdup_printf ("%lu", date_stamp); + xmlSetProp (node, "date", date_str); + } else { + node = root_node->childs; + } + + xmlNodeSetContent (node, annotation_text); + + /* save the modified xml document back to the local repository */ + save_local_annotations (xml_document, digest); + + /* update the metadata date and count */ + annotation_count = count_annotations (xml_document); + info_str = g_strdup_printf ("%lu:%d", date_stamp, annotation_count); + + nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_NOTES_INFO, NULL, info_str); + g_free (info_str); + + /* issue file changed symbol to update the emblem */ + nautilus_file_emit_changed (file); + + /* if the access is global, send it to the server */ + if (eel_strcmp (access, "global") == 0) { + nautilus_annotation_send_to_server (digest, annotation_type, annotation_text, date_str); + } + + /* clean up and we're done */ + xmlFreeDoc (xml_document); + g_free (date_str); + g_free (digest); + g_free (annotations); +} + +/* remove an annotation from a file */ +void nautilus_annotation_remove_annotation (NautilusFile *file, int which_annotation) +{ +} + diff --git a/libnautilus-extensions/nautilus-annotation.h b/libnautilus-extensions/nautilus-annotation.h new file mode 100644 index 000000000..712b276f8 --- /dev/null +++ b/libnautilus-extensions/nautilus-annotation.h @@ -0,0 +1,45 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- + + nautilus-annotation.h: routines for getting and setting xml-based annotations associated + with the digest of a file. + + Copyright (C) 2000 Eazel, Inc. + + 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: Andy Hertzfeld <andy@eazel.com> +*/ + +#ifndef NAUTILUS_ANNOTATION_H +#define NAUTILUS_ANNOTATION_H + +#include <glib.h> +#include <libnautilus-extensions/nautilus-file.h> +#include <libnautilus-extensions/nautilus-metadata.h> + +char * nautilus_annotation_get_annotation (NautilusFile *file); +char * nautilus_annotation_get_display_text (const char* annotation_text); +char * nautilus_annotation_get_annotation_for_display (NautilusFile *file); + +int nautilus_annotation_has_annotation (NautilusFile *file); + +void nautilus_annotation_add_annotation (NautilusFile *file, + const char *annotation_type, + const char *annotation_text, + const char *access); +void nautilus_annotation_remove_annotation (NautilusFile *file, int which_annotation); + +#endif /* NAUTILUS_ANNOTATION_H */ diff --git a/libnautilus-extensions/nautilus-canvas-note-item.c b/libnautilus-extensions/nautilus-canvas-note-item.c new file mode 100644 index 000000000..6f3607090 --- /dev/null +++ b/libnautilus-extensions/nautilus-canvas-note-item.c @@ -0,0 +1,973 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- + + nautilus-canvas-note-item.c: annotation canvas item for nautilus implementation + + Copyright (C) 2001 Eazel, Inc. + + 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. + + based on gnome_canvas_rect_item by Federico Mena Quintero + + Author: Andy Hertzfeld <andy@eazel.com> +*/ + +#include <config.h> +#include <math.h> + +#include <libgnomeui/gnome-canvas.h> +#include <libgnomeui/gnome-canvas-util.h> +#include <libgnomeui/gnome-icon-text.h> + +#include <gnome-xml/parser.h> +#include <gnome-xml/xmlmemory.h> + +#include <libart_lgpl/art_vpath.h> +#include <libart_lgpl/art_svp.h> +#include <libart_lgpl/art_svp_vpath.h> +#include <libart_lgpl/art_rgb_svp.h> + +#include "nautilus-annotation.h" +#include "nautilus-canvas-note-item.h" +#include "nautilus-font-factory.h" +#include <eel/eel-gdk-extensions.h> +#include <eel/eel-gdk-pixbuf-extensions.h> +#include <eel/eel-gnome-extensions.h> +#include <eel/eel-scalable-font.h> +#include <eel/eel-smooth-text-layout.h> +#include <eel/eel-string.h> + +enum { + ARG_0, + ARG_X1, + ARG_Y1, + ARG_X2, + ARG_Y2, + ARG_FILL_COLOR, + ARG_FILL_COLOR_GDK, + ARG_FILL_COLOR_RGBA, + ARG_NOTE_TEXT, + ARG_OUTLINE_COLOR, + ARG_OUTLINE_COLOR_GDK, + ARG_OUTLINE_COLOR_RGBA, + ARG_FILL_STIPPLE, + ARG_OUTLINE_STIPPLE, + ARG_WIDTH_PIXELS, + ARG_WIDTH_UNITS +}; + +#define ANNOTATION_MAX_WIDTH 240 +#define DEFAULT_FONT_SIZE 12 +#define LINE_BREAK_CHARACTERS " -_,;.?/&" + +#define ARROW_HEIGHT 16 +#define MIN_ARROW_HALF_WIDTH 4 +#define MAX_ARROW_HALF_WIDTH 12 + +static void nautilus_canvas_note_item_class_init (NautilusCanvasNoteItemClass *class); +static void nautilus_canvas_note_item_init (NautilusCanvasNoteItem *note_item); +static void nautilus_canvas_note_item_destroy (GtkObject *object); +static void nautilus_canvas_note_item_set_arg (GtkObject *object, + GtkArg *arg, + guint arg_id); +static void nautilus_canvas_note_item_get_arg (GtkObject *object, + GtkArg *arg, + guint arg_id); + +static void nautilus_canvas_note_item_realize (GnomeCanvasItem *item); +static void nautilus_canvas_note_item_unrealize (GnomeCanvasItem *item); +static void nautilus_canvas_note_item_translate (GnomeCanvasItem *item, double dx, double dy); +static void nautilus_canvas_note_item_bounds (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2); + +static void nautilus_canvas_note_item_draw (GnomeCanvasItem *item, GdkDrawable *drawable, int x, int y, int width, int height); +static void nautilus_canvas_note_item_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf); +static void nautilus_canvas_note_item_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags); +static double nautilus_canvas_note_item_point (GnomeCanvasItem *item, double x, double y, int cx, int cy, GnomeCanvasItem **actual_item); + +static GnomeCanvasItemClass *note_item_parent_class; + + +GtkType +nautilus_canvas_note_item_get_type (void) +{ + static GtkType note_item_type = 0; + + if (!note_item_type) { + GtkTypeInfo note_item_info = { + "NautilusCanvasNoteItem", + sizeof (NautilusCanvasNoteItem), + sizeof (NautilusCanvasNoteItemClass), + (GtkClassInitFunc) nautilus_canvas_note_item_class_init, + (GtkObjectInitFunc) nautilus_canvas_note_item_init, + NULL, /* reserved_1 */ + NULL, /* reserved_2 */ + (GtkClassInitFunc) NULL + }; + + note_item_type = gtk_type_unique (gnome_canvas_item_get_type (), ¬e_item_info); + } + + return note_item_type; +} + +static void +nautilus_canvas_note_item_class_init (NautilusCanvasNoteItemClass *class) +{ + GtkObjectClass *object_class; + GnomeCanvasItemClass *item_class; + + object_class = (GtkObjectClass *) class; + item_class = (GnomeCanvasItemClass *) class; + + note_item_parent_class = gtk_type_class (gnome_canvas_item_get_type ()); + + gtk_object_add_arg_type ("NautilusCanvasNoteItem::x1", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_X1); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::y1", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_Y1); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::x2", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_X2); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::y2", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_Y2); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::fill_color", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_FILL_COLOR); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::fill_color_gdk", GTK_TYPE_GDK_COLOR, GTK_ARG_READWRITE, ARG_FILL_COLOR_GDK); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::fill_color_rgba", GTK_TYPE_UINT, GTK_ARG_READWRITE, ARG_FILL_COLOR_RGBA); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::note_text", GTK_TYPE_STRING, GTK_ARG_READWRITE, ARG_NOTE_TEXT); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::outline_color", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_OUTLINE_COLOR); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::outline_color_gdk", GTK_TYPE_GDK_COLOR, GTK_ARG_READWRITE, ARG_OUTLINE_COLOR_GDK); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::outline_color_rgba", GTK_TYPE_UINT, GTK_ARG_READWRITE, ARG_OUTLINE_COLOR_RGBA); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::fill_stipple", GTK_TYPE_GDK_WINDOW, GTK_ARG_READWRITE, ARG_FILL_STIPPLE); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::outline_stipple", GTK_TYPE_GDK_WINDOW, GTK_ARG_READWRITE, ARG_OUTLINE_STIPPLE); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::width_pixels", GTK_TYPE_UINT, GTK_ARG_WRITABLE, ARG_WIDTH_PIXELS); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::width_units", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_WIDTH_UNITS); + + object_class->destroy = nautilus_canvas_note_item_destroy; + object_class->set_arg = nautilus_canvas_note_item_set_arg; + object_class->get_arg = nautilus_canvas_note_item_get_arg; + + item_class->realize = nautilus_canvas_note_item_realize; + item_class->unrealize = nautilus_canvas_note_item_unrealize; + item_class->translate = nautilus_canvas_note_item_translate; + item_class->bounds = nautilus_canvas_note_item_bounds; + + item_class->draw = nautilus_canvas_note_item_draw; + item_class->point = nautilus_canvas_note_item_point; + item_class->update = nautilus_canvas_note_item_update; + item_class->render = nautilus_canvas_note_item_render; +} + +static void +nautilus_canvas_note_item_init (NautilusCanvasNoteItem *note_item) +{ + note_item->width = 0.0; + note_item->fill_svp = NULL; + note_item->outline_svp = NULL; +} + +static void +nautilus_canvas_note_item_destroy (GtkObject *object) +{ + NautilusCanvasNoteItem *note_item; + + g_return_if_fail (object != NULL); + g_return_if_fail (NAUTILUS_IS_CANVAS_NOTE_ITEM (object)); + + note_item = NAUTILUS_CANVAS_NOTE_ITEM (object); + + if (note_item->fill_stipple) + gdk_bitmap_unref (note_item->fill_stipple); + + if (note_item->outline_stipple) + gdk_bitmap_unref (note_item->outline_stipple); + + if (note_item->fill_svp) + art_svp_free (note_item->fill_svp); + + if (note_item->outline_svp) + art_svp_free (note_item->outline_svp); + + if (note_item->note_text) + g_free (note_item->note_text); + + if (GTK_OBJECT_CLASS (note_item_parent_class)->destroy) + (* GTK_OBJECT_CLASS (note_item_parent_class)->destroy) (object); +} + +static void get_bounds (NautilusCanvasNoteItem *note_item, double *px1, double *py1, double *px2, double *py2) +{ + GnomeCanvasItem *item; + double x1, y1, x2, y2; + int cx1, cy1, cx2, cy2; + double hwidth; + + item = GNOME_CANVAS_ITEM (note_item); + + if (note_item->width_pixels) + hwidth = (note_item->width / item->canvas->pixels_per_unit) / 2.0; + else + hwidth = note_item->width / 2.0; + + x1 = note_item->x1; + y1 = note_item->y1; + x2 = note_item->x2; + y2 = note_item->y2; + + gnome_canvas_item_i2w (item, &x1, &y1); + gnome_canvas_item_i2w (item, &x2, &y2); + gnome_canvas_w2c (item->canvas, x1 - hwidth, y1 - hwidth, &cx1, &cy1); + gnome_canvas_w2c (item->canvas, x2 + hwidth, y2 + hwidth, &cx2, &cy2); + *px1 = cx1; + *py1 = cy1; + *px2 = cx2; + *py2 = cy2; + + /* Some safety fudging */ + + *px1 -= 2; + *py1 -= 2; + *px2 += 2; + *py2 += 2; +} + +/* Convenience function to set a GC's foreground color to the specified pixel value */ +static void +set_gc_foreground (GdkGC *gc, gulong pixel) +{ + GdkColor c; + + if (!gc) + return; + + c.pixel = pixel; + gdk_gc_set_foreground (gc, &c); +} + +/* Sets the stipple pattern for the specified gc */ +static void +set_stipple (GdkGC *gc, GdkBitmap **internal_stipple, GdkBitmap *stipple, int reconfigure) +{ + if (*internal_stipple && !reconfigure) + gdk_bitmap_unref (*internal_stipple); + + *internal_stipple = stipple; + if (stipple && !reconfigure) + gdk_bitmap_ref (stipple); + + if (gc) { + if (stipple) { + gdk_gc_set_stipple (gc, stipple); + gdk_gc_set_fill (gc, GDK_STIPPLED); + } else + gdk_gc_set_fill (gc, GDK_SOLID); + } +} + +/* Recalculate the outline width of the rectangle/ellipse and set it in its GC */ +static void +set_outline_gc_width (NautilusCanvasNoteItem *note_item) +{ + int width; + + if (!note_item->outline_gc) + return; + + if (note_item->width_pixels) + width = (int) note_item->width; + else + width = (int) (note_item->width * note_item->item.canvas->pixels_per_unit + 0.5); + + gdk_gc_set_line_attributes (note_item->outline_gc, width, + GDK_LINE_SOLID, GDK_CAP_PROJECTING, GDK_JOIN_MITER); +} + +/* utility to update the canvas item bounding box from the note item's private bounding box */ +static void +update_item_bounding_box (NautilusCanvasNoteItem *note_item) +{ + GnomeCanvasItem *item; + item = GNOME_CANVAS_ITEM (note_item); + + item->x1 = note_item->x1; + item->y1 = note_item->y1; + item->x2 = note_item->x2 + 1; + item->y2 = note_item->y2 + 1; +} + +static void +nautilus_canvas_note_item_set_note_text (NautilusCanvasNoteItem *note_item, const char *new_text) +{ + char *display_text; + int text_width, height, width; + int font_height; + GnomeCanvasItem *item; + EelScalableFont *scalable_font; + GdkFont *font; + EelDimensions dimensions; + EelSmoothTextLayout *smooth_text_layout; + + item = GNOME_CANVAS_ITEM (note_item); + + if (note_item->note_text) { + g_free (note_item->note_text); + } + + height = 0; width = 0; /* to avoid compiler complaint */ + note_item->note_text = g_strdup (new_text); + + /* set the width and height based on the display text */ + /* this will get more sophisticated as we get fancier */ + display_text = nautilus_annotation_get_display_text (new_text); + + if (item->canvas->aa) { + scalable_font = eel_scalable_font_get_default_font (); + + + smooth_text_layout = eel_smooth_text_layout_new ( + display_text, strlen(display_text), + scalable_font, DEFAULT_FONT_SIZE, TRUE); + + dimensions = eel_smooth_text_layout_get_dimensions (smooth_text_layout); + text_width = dimensions.width + 8; + height = dimensions.height + 4; + height += ARROW_HEIGHT; + gtk_object_unref (GTK_OBJECT (scalable_font)); + gtk_object_destroy (GTK_OBJECT (smooth_text_layout)); + + } else { + font = nautilus_font_factory_get_font_from_preferences (DEFAULT_FONT_SIZE); + text_width = 8 + gdk_text_measure (font, display_text, strlen (display_text)); + font_height = gdk_text_height (font, display_text, strlen (display_text)); + height = font_height * (1 + (text_width / ANNOTATION_MAX_WIDTH)); + gdk_font_unref (font); + } + + width = (text_width < ANNOTATION_MAX_WIDTH) ? text_width : ANNOTATION_MAX_WIDTH; + + /* add some vertical slop for descenders and incorporate scale factor */ + note_item->x2 = floor (note_item->x1 + (width / item->canvas->pixels_per_unit) + .5); + note_item->y2 = floor (note_item->y1 + 4.0 + (height / item->canvas->pixels_per_unit) + .5); + + + update_item_bounding_box (note_item); + + g_free (display_text); +} + +static void +nautilus_canvas_note_item_set_arg (GtkObject *object, GtkArg *arg, guint arg_id) +{ + GnomeCanvasItem *item; + NautilusCanvasNoteItem *note_item; + GdkColor color = { 0, 0, 0, 0, }; + GdkColor *pcolor; + int have_pixel; + + item = GNOME_CANVAS_ITEM (object); + note_item = NAUTILUS_CANVAS_NOTE_ITEM (object); + have_pixel = FALSE; + + switch (arg_id) { + case ARG_X1: + note_item->x1 = GTK_VALUE_DOUBLE (*arg); + update_item_bounding_box (note_item); + gnome_canvas_item_request_update (item); + break; + + case ARG_Y1: + note_item->y1 = GTK_VALUE_DOUBLE (*arg); + update_item_bounding_box (note_item); + gnome_canvas_item_request_update (item); + break; + + case ARG_X2: + note_item->x2 = GTK_VALUE_DOUBLE (*arg); + update_item_bounding_box (note_item); + gnome_canvas_item_request_update (item); + break; + + case ARG_Y2: + note_item->y2 = GTK_VALUE_DOUBLE (*arg); + update_item_bounding_box (note_item); + gnome_canvas_item_request_update (item); + break; + + case ARG_FILL_COLOR: + case ARG_FILL_COLOR_GDK: + case ARG_FILL_COLOR_RGBA: + switch (arg_id) { + case ARG_FILL_COLOR: + gdk_color_parse (GTK_VALUE_STRING (*arg), &color); + + note_item->fill_color = ((color.red & 0xff00) << 16 | + (color.green & 0xff00) << 8 | + (color.blue & 0xff00) | + 0xff); + break; + + case ARG_FILL_COLOR_GDK: + pcolor = GTK_VALUE_BOXED (*arg); + if (pcolor) { + color = *pcolor; + gdk_color_context_query_color (item->canvas->cc, &color); + have_pixel = TRUE; + } + + note_item->fill_color = ((color.red & 0xff00) << 16 | + (color.green & 0xff00) << 8 | + (color.blue & 0xff00) | + 0xff); + break; + + case ARG_FILL_COLOR_RGBA: + note_item->fill_color = GTK_VALUE_UINT (*arg); + break; + } + + if (have_pixel) + note_item->fill_pixel = color.pixel; + else + note_item->fill_pixel = gnome_canvas_get_color_pixel (item->canvas, note_item->fill_color); + + if (!item->canvas->aa) + set_gc_foreground (note_item->fill_gc, note_item->fill_pixel); + + gnome_canvas_item_request_redraw_svp (item, note_item->fill_svp); + break; + + case ARG_OUTLINE_COLOR: + case ARG_OUTLINE_COLOR_GDK: + case ARG_OUTLINE_COLOR_RGBA: + switch (arg_id) { + case ARG_OUTLINE_COLOR: + gdk_color_parse (GTK_VALUE_STRING (*arg), &color); + + note_item->outline_color = ((color.red & 0xff00) << 16 | + (color.green & 0xff00) << 8 | + (color.blue & 0xff00) | + 0xff); + break; + + case ARG_OUTLINE_COLOR_GDK: + pcolor = GTK_VALUE_BOXED (*arg); + if (pcolor) { + color = *pcolor; + gdk_color_context_query_color (item->canvas->cc, &color); + have_pixel = TRUE; + } + + note_item->outline_color = ((color.red & 0xff00) << 16 | + (color.green & 0xff00) << 8 | + (color.blue & 0xff00) | + 0xff); + break; + + case ARG_OUTLINE_COLOR_RGBA: + note_item->outline_color = GTK_VALUE_UINT (*arg); + break; + } + + if (have_pixel) + note_item->outline_pixel = color.pixel; + else + note_item->outline_pixel = gnome_canvas_get_color_pixel (item->canvas, + note_item->outline_color); + + if (!item->canvas->aa) + set_gc_foreground (note_item->outline_gc, note_item->outline_pixel); + + gnome_canvas_item_request_redraw_svp (item, note_item->outline_svp); + break; + + case ARG_NOTE_TEXT: + nautilus_canvas_note_item_set_note_text (note_item, GTK_VALUE_STRING (*arg)); + break; + + case ARG_FILL_STIPPLE: + if (!item->canvas->aa) + set_stipple (note_item->fill_gc, ¬e_item->fill_stipple, GTK_VALUE_BOXED (*arg), FALSE); + + break; + + case ARG_OUTLINE_STIPPLE: + if (!item->canvas->aa) + set_stipple (note_item->outline_gc, ¬e_item->outline_stipple, GTK_VALUE_BOXED (*arg), FALSE); + break; + + case ARG_WIDTH_PIXELS: + note_item->width = GTK_VALUE_UINT (*arg); + note_item->width_pixels = TRUE; + if (!item->canvas->aa) + set_outline_gc_width (note_item); + + gnome_canvas_item_request_update (item); + break; + + case ARG_WIDTH_UNITS: + note_item->width = fabs (GTK_VALUE_DOUBLE (*arg)); + note_item->width_pixels = FALSE; + if (!item->canvas->aa) + set_outline_gc_width (note_item); + + gnome_canvas_item_request_update (item); + break; + + default: + break; + } +} + +/* Allocates a GdkColor structure filled with the specified pixel, and puts it into the specified + * arg for returning it in the get_arg method. + */ +static void +get_color_arg (NautilusCanvasNoteItem *note_item, gulong pixel, GtkArg *arg) +{ + GdkColor *color; + + color = g_new (GdkColor, 1); + color->pixel = pixel; + gdk_color_context_query_color (GNOME_CANVAS_ITEM (note_item)->canvas->cc, color); + GTK_VALUE_BOXED (*arg) = color; +} + +static void +nautilus_canvas_note_item_get_arg (GtkObject *object, GtkArg *arg, guint arg_id) +{ + NautilusCanvasNoteItem *note_item; + GnomeCanvasItem *item; + + item = GNOME_CANVAS_ITEM (object); + note_item = NAUTILUS_CANVAS_NOTE_ITEM (object); + + switch (arg_id) { + case ARG_X1: + GTK_VALUE_DOUBLE (*arg) = note_item->x1; + break; + + case ARG_Y1: + GTK_VALUE_DOUBLE (*arg) = note_item->y1; + break; + + case ARG_X2: + GTK_VALUE_DOUBLE (*arg) = note_item->x2; + break; + + case ARG_Y2: + GTK_VALUE_DOUBLE (*arg) = note_item->y2; + break; + + case ARG_FILL_COLOR_GDK: + get_color_arg (note_item, note_item->fill_pixel, arg); + break; + + case ARG_OUTLINE_COLOR_GDK: + get_color_arg (note_item, note_item->outline_pixel, arg); + break; + + case ARG_FILL_COLOR_RGBA: + GTK_VALUE_UINT (*arg) = note_item->fill_color; + break; + + case ARG_OUTLINE_COLOR_RGBA: + GTK_VALUE_UINT (*arg) = note_item->outline_color; + break; + + case ARG_FILL_STIPPLE: + GTK_VALUE_BOXED (*arg) = note_item->fill_stipple; + break; + + case ARG_OUTLINE_STIPPLE: + GTK_VALUE_BOXED (*arg) = note_item->outline_stipple; + break; + + case ARG_NOTE_TEXT: + GTK_VALUE_STRING (*arg) = note_item->note_text; + break; + default: + arg->type = GTK_TYPE_INVALID; + break; + } +} + +static void +nautilus_canvas_note_item_realize (GnomeCanvasItem *item) +{ + NautilusCanvasNoteItem *note_item; + + note_item = NAUTILUS_CANVAS_NOTE_ITEM (item); + + if (note_item_parent_class->realize) + (* note_item_parent_class->realize) (item); + + if (!item->canvas->aa) { + note_item->fill_gc = gdk_gc_new (item->canvas->layout.bin_window); + note_item->outline_gc = gdk_gc_new (item->canvas->layout.bin_window); + } +} + +static void +nautilus_canvas_note_item_unrealize (GnomeCanvasItem *item) +{ + NautilusCanvasNoteItem *note_item; + + note_item = NAUTILUS_CANVAS_NOTE_ITEM (item); + + if (!item->canvas->aa) { + gdk_gc_unref (note_item->fill_gc); + note_item->fill_gc = NULL; + gdk_gc_unref (note_item->outline_gc); + note_item->outline_gc = NULL; + } + + if (note_item_parent_class->unrealize) + (* note_item_parent_class->unrealize) (item); +} + +static void +nautilus_canvas_note_item_translate (GnomeCanvasItem *item, double dx, double dy) +{ + NautilusCanvasNoteItem *note_item; + + note_item = NAUTILUS_CANVAS_NOTE_ITEM (item); + + note_item->x1 += dx; + note_item->y1 += dy; + note_item->x2 += dx; + note_item->y2 += dy; + + update_item_bounding_box (note_item); + + if (item->canvas->aa) { + gnome_canvas_item_request_update (item); + } +} + +static void +nautilus_canvas_note_item_bounds (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2) +{ + NautilusCanvasNoteItem *note_item; + double hwidth; + + note_item = NAUTILUS_CANVAS_NOTE_ITEM (item); + + if (note_item->width_pixels) + hwidth = (note_item->width / item->canvas->pixels_per_unit) / 2.0; + else + hwidth = note_item->width / 2.0; + + *x1 = note_item->x1 - hwidth; + *y1 = note_item->y1 - hwidth; + *x2 = note_item->x2 + hwidth; + *y2 = note_item->y2 + hwidth; +} + +/* utility routine to map raw annotation text into text to be displayed */ +/* for now this is pretty naive and only handles free-form text, just returning + * the first suitable annotation it can find + */ + +/* utility routine to draw a text string into the passed-in item */ +static void +draw_item_aa_text (GnomeCanvasBuf *buf, GnomeCanvasItem *item, const char *note_text) +{ + EelScalableFont *font; + GdkPixbuf *text_pixbuf; + ArtIRect item_bounds, dest_bounds; + int width, height; + EelSmoothTextLayout *smooth_text_layout; + + font = eel_scalable_font_get_default_font (); + + eel_gnome_canvas_item_get_canvas_bounds (item, &item_bounds); + width = item_bounds.x1 - item_bounds.x0; + height = item_bounds.y1 - item_bounds.y0; + + text_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + TRUE, + 8, + width, + height); + eel_gdk_pixbuf_fill_rectangle_with_color (text_pixbuf, NULL, + EEL_RGBA_COLOR_PACK (0, 0, 0, 0)); + + smooth_text_layout = eel_smooth_text_layout_new ( + note_text, strlen(note_text), + font, DEFAULT_FONT_SIZE, TRUE); + + eel_smooth_text_layout_set_line_wrap_width (smooth_text_layout, width - 4); + + dest_bounds.x0 = 0; + dest_bounds.y0 = ARROW_HEIGHT; + dest_bounds.x1 = width; + dest_bounds.y1 = height; + + eel_smooth_text_layout_draw_to_pixbuf + (smooth_text_layout, text_pixbuf, + 0, 0, &dest_bounds, GTK_JUSTIFY_LEFT, + FALSE, EEL_RGBA_COLOR_OPAQUE_BLACK, + EEL_OPACITY_FULLY_OPAQUE); + + gtk_object_destroy (GTK_OBJECT (smooth_text_layout)); + + eel_gnome_canvas_draw_pixbuf (buf, text_pixbuf, item_bounds.x0 + 4, item_bounds.y0 + 2); + + gdk_pixbuf_unref (text_pixbuf); + gtk_object_unref (GTK_OBJECT (font)); +} + +static void +nautilus_canvas_note_item_render (GnomeCanvasItem *item, + GnomeCanvasBuf *buf) +{ + NautilusCanvasNoteItem *note_item; + char *display_text; + + note_item = NAUTILUS_CANVAS_NOTE_ITEM (item); + + if (note_item->fill_svp != NULL) { + gnome_canvas_render_svp (buf, note_item->fill_svp, note_item->fill_color); + } + + /* draw the annotation text, if necessary */ + if (note_item->note_text) { + display_text = nautilus_annotation_get_display_text (note_item->note_text); + if (display_text && strlen (display_text)) { + draw_item_aa_text (buf, item, display_text); + } + g_free (display_text); + } + + if (note_item->outline_svp != NULL) { + gnome_canvas_render_svp (buf, note_item->outline_svp, note_item->outline_color); + } +} + +static void +nautilus_canvas_note_item_draw (GnomeCanvasItem *item, GdkDrawable *drawable, int x, int y, int width, int height) +{ + NautilusCanvasNoteItem *note_item; + GdkFont *font; + char* display_text; + double i2w[6], w2c[6], i2c[6]; + int x1, y1, x2, y2; + ArtPoint i1, i2; + ArtPoint c1, c2; + GnomeIconTextInfo *text_info; + + note_item = NAUTILUS_CANVAS_NOTE_ITEM (item); + + /* Get canvas pixel coordinates */ + gnome_canvas_item_i2w_affine (item, i2w); + gnome_canvas_w2c_affine (item->canvas, w2c); + art_affine_multiply (i2c, i2w, w2c); + + i1.x = note_item->x1; + i1.y = note_item->y1; + i2.x = note_item->x2; + i2.y = note_item->y2; + art_affine_point (&c1, &i1, i2c); + art_affine_point (&c2, &i2, i2c); + x1 = c1.x; + y1 = c1.y; + x2 = c2.x; + y2 = c2.y; + + if (note_item->fill_stipple) + gnome_canvas_set_stipple_origin (item->canvas, note_item->fill_gc); + + gdk_draw_rectangle (drawable, + note_item->fill_gc, + TRUE, + x1 - x, + y1 - y, + x2 - x1 + 1, + y2 - y1 + 1); + + /* draw the annotation text */ + if (note_item->note_text) { + font = nautilus_font_factory_get_font_from_preferences (DEFAULT_FONT_SIZE); + display_text = nautilus_annotation_get_display_text (note_item->note_text); + + text_info = gnome_icon_layout_text + (font, display_text, + LINE_BREAK_CHARACTERS, + x2 - x1 - 2, TRUE); + + gnome_icon_paint_text (text_info, drawable, note_item->outline_gc, + x1 - x + 4, y1 - y + 4, GTK_JUSTIFY_LEFT); + gnome_icon_text_info_free (text_info); + + gdk_font_unref (font); + g_free (display_text); + } + + if (note_item->outline_stipple) + gnome_canvas_set_stipple_origin (item->canvas, note_item->outline_gc); + + gdk_draw_rectangle (drawable, + note_item->outline_gc, + FALSE, + x1 - x, + y1 - y, + x2 - x1, + y2 - y1); +} + +static double +nautilus_canvas_note_item_point (GnomeCanvasItem *item, double x, double y, int cx, int cy, GnomeCanvasItem **actual_item) +{ + NautilusCanvasNoteItem *note_item; + double x1, y1, x2, y2; + double hwidth; + double dx, dy; + + note_item = NAUTILUS_CANVAS_NOTE_ITEM (item); + + *actual_item = item; + + /* Find the bounds for the rectangle plus its outline width */ + + x1 = note_item->x1; + y1 = note_item->y1; + x2 = note_item->x2; + y2 = note_item->y2; + + if (note_item->width_pixels) + hwidth = (note_item->width / item->canvas->pixels_per_unit) / 2.0; + else + hwidth = note_item->width / 2.0; + + x1 -= hwidth; + y1 -= hwidth; + x2 += hwidth; + y2 += hwidth; + + /* Is point inside rectangle (which can be hollow if it has no fill set)? */ + + if ((x >= x1) && (y >= y1) && (x <= x2) && (y <= y2)) { + return 0.0; + } + + /* Point is outside rectangle */ + + if (x < x1) + dx = x1 - x; + else if (x > x2) + dx = x - x2; + else + dx = 0.0; + + if (y < y1) + dy = y1 - y; + else if (y > y2) + dy = y - y2; + else + dy = 0.0; + + return sqrt (dx * dx + dy * dy); +} + +static void +nautilus_canvas_note_item_update (GnomeCanvasItem *item, double affine[6], ArtSVP *clip_path, gint flags) +{ + NautilusCanvasNoteItem *note_item; + ArtVpath vpath[9]; + ArtVpath *vpath2; + ArtSVP *stroke_svp; + double x0, y0, x1, y1; + double round_off_amount; + double midpoint; + double arrow_half_width; + note_item = NAUTILUS_CANVAS_NOTE_ITEM (item); + + if (note_item_parent_class->update) + (* note_item_parent_class->update) (item, affine, clip_path, flags); + + if (item->canvas->aa) { + x0 = note_item->x1; + y0 = note_item->y1 + ARROW_HEIGHT; + x1 = note_item->x2; + y1 = note_item->y2; + + round_off_amount = item->canvas->pixels_per_unit / 2; + + gnome_canvas_item_reset_bounds (item); + midpoint = (x1 + x0) / 2; + arrow_half_width = (x1 - x0) / 16; + arrow_half_width = CLAMP (arrow_half_width, MIN_ARROW_HALF_WIDTH, MAX_ARROW_HALF_WIDTH); + + vpath[0].code = ART_MOVETO; + vpath[0].x = x0 - round_off_amount; + vpath[0].y = y0 - round_off_amount; + + vpath[1].code = ART_LINETO; + vpath[1].x = x0 - round_off_amount; + vpath[1].y = y1 + round_off_amount; + + vpath[2].code = ART_LINETO; + vpath[2].x = x1 + round_off_amount; + vpath[2].y = y1 + round_off_amount; + + vpath[3].code = ART_LINETO; + vpath[3].x = x1 + round_off_amount; + vpath[3].y = y0 - round_off_amount; + + vpath[4].code = ART_LINETO; + vpath[4].x = midpoint + arrow_half_width + round_off_amount; + vpath[4].y = y0 - round_off_amount; + + vpath[5].code = ART_LINETO; + vpath[5].x = midpoint + round_off_amount; + vpath[5].y = note_item->y1 - round_off_amount; + + vpath[6].code = ART_LINETO; + vpath[6].x = midpoint - arrow_half_width - round_off_amount; + vpath[6].y = y0 - round_off_amount; + + vpath[7].code = ART_LINETO; + vpath[7].x = x0 - round_off_amount; + vpath[7].y = y0 - round_off_amount; + + vpath[8].code = ART_END; + vpath[8].x = 0; + vpath[8].y = 0; + + vpath2 = art_vpath_affine_transform (vpath, affine); + + gnome_canvas_item_update_svp_clip (item, ¬e_item->fill_svp, art_svp_from_vpath (vpath2), clip_path); + + stroke_svp = art_svp_vpath_stroke (vpath2, + ART_PATH_STROKE_JOIN_MITER, + ART_PATH_STROKE_CAP_BUTT, + (note_item->width_pixels) ? note_item->width : (note_item->width * item->canvas->pixels_per_unit), + 4, + 25); + + gnome_canvas_item_update_svp_clip (item, ¬e_item->outline_svp, stroke_svp, clip_path); + art_free (vpath2); + + eel_gnome_canvas_item_request_redraw + (GNOME_CANVAS_ITEM (item)); + + } else { + /* xlib rendering - just update the bbox */ + + set_gc_foreground (note_item->fill_gc, note_item->fill_pixel); + set_gc_foreground (note_item->outline_gc, note_item->outline_pixel); + set_stipple (note_item->fill_gc, ¬e_item->fill_stipple, note_item->fill_stipple, TRUE); + set_stipple (note_item->outline_gc, ¬e_item->outline_stipple, note_item->outline_stipple, TRUE); + set_outline_gc_width (note_item); + + get_bounds (note_item, &x0, &y0, &x1, &y1); + gnome_canvas_update_bbox (item, x0, y0, x1, y1); + } +} + diff --git a/libnautilus-extensions/nautilus-canvas-note-item.h b/libnautilus-extensions/nautilus-canvas-note-item.h new file mode 100644 index 000000000..3c763c3df --- /dev/null +++ b/libnautilus-extensions/nautilus-canvas-note-item.h @@ -0,0 +1,90 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- + + nautilus-canvas-note-item.h: annotation canvas item for Nautilus + + Copyright (C) 2001 Eazel, Inc. + + 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. + + based on gnome_canvas_rect_item by Federico Mena Quintero + + Author: Andy Hertzfeld <andy@eazel.com> +*/ + +#ifndef NAUTILUS_CANVAS_NOTE_ITEM_H +#define NAUTILUS_CANVAS_NOTE_ITEM_H + +#include <libgnome/gnome-defs.h> +#include <libgnomeui/gnome-canvas.h> + +#include <libart_lgpl/art_svp.h> + +BEGIN_GNOME_DECLS + +#define NAUTILUS_TYPE_CANVAS_NOTE_ITEM (nautilus_canvas_note_item_get_type ()) +#define NAUTILUS_CANVAS_NOTE_ITEM(obj) (GTK_CHECK_CAST ((obj), NAUTILUS_TYPE_CANVAS_NOTE_ITEM, NautilusCanvasNoteItem)) +#define NAUTILUS_CANVAS_NOTE_ITEM_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), NAUTILUS_CANVAS_NOTE_ITEM, NautilusCanvasNoteItemClass)) +#define NAUTILUS_IS_CANVAS_NOTE_ITEM(obj) (GTK_CHECK_TYPE ((obj), NAUTILUS_TYPE_CANVAS_NOTE_ITEM)) +#define NAUTILUS_IS_CANVAS_NOTE_ITEM_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_CANVAS_NOTE_ITEM)) + + +typedef struct _NautilusCanvasNoteItem NautilusCanvasNoteItem; +typedef struct _NautilusCanvasNoteItemClass NautilusCanvasNoteItemClass; + +struct _NautilusCanvasNoteItem { + GnomeCanvasItem item; + + double x1; /* canvas coordinates of bounding box */ + double y1; + double x2; + double y2; + + double width; /* Outline width */ + + guint fill_color; /* Fill color, RGBA */ + guint outline_color; /* Outline color, RGBA */ + + gulong fill_pixel; /* Fill color */ + gulong outline_pixel; /* Outline color */ + + GdkBitmap *fill_stipple; /* Stipple for fill */ + GdkBitmap *outline_stipple; /* Stipple for outline */ + + GdkGC *fill_gc; /* GC for filling */ + GdkGC *outline_gc; /* GC for outline */ + + /* text message */ + char *note_text; /* text for annotation */ + + /* Antialiased specific stuff follows */ + ArtSVP *fill_svp; /* The SVP for the filled shape */ + ArtSVP *outline_svp; /* The SVP for the outline shape */ + + /* Configuration flags */ + unsigned int width_pixels : 1; /* Is outline width specified in pixels or units? */ +}; + +struct _NautilusCanvasNoteItemClass { + GnomeCanvasItemClass parent_class; +}; + +/* Standard Gtk function */ +GtkType nautilus_canvas_note_item_get_type (void); + + +END_GNOME_DECLS + +#endif diff --git a/libnautilus-extensions/nautilus-file-utilities.c b/libnautilus-extensions/nautilus-file-utilities.c index d015879cd..5f6c5c990 100644 --- a/libnautilus-extensions/nautilus-file-utilities.c +++ b/libnautilus-extensions/nautilus-file-utilities.c @@ -224,7 +224,7 @@ nautilus_get_user_main_directory (void) /* If this fails to create the directory, nautilus_application_startup will * notice and refuse to launch. */ - + /* install the default link sets */ nautilus_link_set_install (user_main_directory, "apps"); nautilus_link_set_install (user_main_directory, "home"); diff --git a/libnautilus-extensions/nautilus-file.c b/libnautilus-extensions/nautilus-file.c index 8bd2b7c23..e251032dc 100644 --- a/libnautilus-extensions/nautilus-file.c +++ b/libnautilus-extensions/nautilus-file.c @@ -25,6 +25,7 @@ #include <config.h> #include "nautilus-file.h" +#include "nautilus-annotation.h" #include "nautilus-directory-metafile.h" #include "nautilus-directory-notify.h" #include "nautilus-directory-private.h" @@ -1500,6 +1501,10 @@ prepend_automatic_emblem_names (NautilusFile *file, { /* Prepend in reverse order. */ + if (nautilus_annotation_has_annotation (file) > 0) { + names = g_list_prepend + (names, g_strdup (NAUTILUS_FILE_EMBLEM_ANNOTATION)); + } if (nautilus_file_is_in_trash (file)) { names = g_list_prepend (names, g_strdup (NAUTILUS_FILE_EMBLEM_NAME_TRASH)); diff --git a/libnautilus-extensions/nautilus-file.h b/libnautilus-extensions/nautilus-file.h index 8a4892969..820cae932 100644 --- a/libnautilus-extensions/nautilus-file.h +++ b/libnautilus-extensions/nautilus-file.h @@ -71,6 +71,7 @@ typedef enum { #define NAUTILUS_FILE_EMBLEM_NAME_CANT_READ "noread" #define NAUTILUS_FILE_EMBLEM_NAME_CANT_WRITE "nowrite" #define NAUTILUS_FILE_EMBLEM_NAME_TRASH "trash" +#define NAUTILUS_FILE_EMBLEM_ANNOTATION "note" typedef void (*NautilusFileCallback) (NautilusFile *file, gpointer callback_data); diff --git a/libnautilus-extensions/nautilus-global-preferences.c b/libnautilus-extensions/nautilus-global-preferences.c index c9d59c17a..5603d7dee 100644 --- a/libnautilus-extensions/nautilus-global-preferences.c +++ b/libnautilus-extensions/nautilus-global-preferences.c @@ -368,6 +368,18 @@ static const PreferenceDefault preference_defaults[] = { { NAUTILUS_USER_LEVEL_NOVICE, GINT_TO_POINTER (TRUE) }, { USER_LEVEL_NONE } }, + { NAUTILUS_PREFERENCES_LOOKUP_ANNOTATIONS, + PREFERENCE_BOOLEAN, + NAUTILUS_USER_LEVEL_INTERMEDIATE, + { NAUTILUS_USER_LEVEL_NOVICE, GINT_TO_POINTER (FALSE) }, + { USER_LEVEL_NONE } + }, + { NAUTILUS_PREFERENCES_DISPLAY_ANNOTATIONS, + PREFERENCE_BOOLEAN, + NAUTILUS_USER_LEVEL_INTERMEDIATE, + { NAUTILUS_USER_LEVEL_NOVICE, GINT_TO_POINTER (FALSE) }, + { USER_LEVEL_NONE } + }, { NAUTILUS_PREFERENCES_PREVIEW_SOUND, PREFERENCE_INTEGER, NAUTILUS_USER_LEVEL_INTERMEDIATE, @@ -654,6 +666,14 @@ global_preferences_install_defaults (void) preference_defaults[i].visible_user_level); } + nautilus_preferences_default_set_boolean (NAUTILUS_PREFERENCES_LOOKUP_ANNOTATIONS, + NAUTILUS_USER_LEVEL_NOVICE, + FALSE); + + nautilus_preferences_default_set_boolean (NAUTILUS_PREFERENCES_DISPLAY_ANNOTATIONS, + NAUTILUS_USER_LEVEL_NOVICE, + FALSE); + /* Add the gnome-vfs path to the list of monitored directories - for proxy settings */ nautilus_preferences_monitor_directory (SYSTEM_GNOME_VFS_PATH); @@ -1041,6 +1061,24 @@ static PreferenceDialogItem tradeoffs_items[] = { { NULL } }; +static PreferenceDialogItem annotation_items[] = { + { N_("Lookup File Annotations"), + NAUTILUS_PREFERENCES_LOOKUP_ANNOTATIONS, + N_("Use the service to lookup file annotations"), + NAUTILUS_PREFERENCE_ITEM_BOOLEAN, + NULL, + 0 + }, + { N_("Display File Annotations"), + NAUTILUS_PREFERENCES_DISPLAY_ANNOTATIONS, + N_("Display emblems for file annotations"), + NAUTILUS_PREFERENCE_ITEM_BOOLEAN, + NULL, + 0 + }, + { NULL, NULL, NULL, 0, NULL, 0 } +}; + static GtkWidget * global_preferences_create_dialog (void) { @@ -1105,6 +1143,11 @@ global_preferences_create_dialog (void) _("Speed Tradeoffs"), tradeoffs_items); + /* Annotations */ + global_preferences_populate_pane (preference_box, + _("Annotations"), + annotation_items); + /* Update the dialog so that the right items show up based on the current user level */ nautilus_preferences_dialog_update (NAUTILUS_PREFERENCES_DIALOG (prefs_dialog)); diff --git a/libnautilus-extensions/nautilus-global-preferences.h b/libnautilus-extensions/nautilus-global-preferences.h index d52cd42fe..fdaac1e2b 100644 --- a/libnautilus-extensions/nautilus-global-preferences.h +++ b/libnautilus-extensions/nautilus-global-preferences.h @@ -110,6 +110,10 @@ enum NAUTILUS_DEFAULT_FOLDER_VIEWER_LIST_VIEW }; +/* enabling annotations */ +#define NAUTILUS_PREFERENCES_LOOKUP_ANNOTATIONS "preferences/lookup_annotations" +#define NAUTILUS_PREFERENCES_DISPLAY_ANNOTATIONS "preferences/display_annotations" + /* Icon View */ #define NAUTILUS_PREFERENCES_ICON_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER "icon-view/default_sort_in_reverse_order" #define NAUTILUS_PREFERENCES_ICON_VIEW_DEFAULT_SORT_ORDER "icon-view/default_sort_order" diff --git a/libnautilus-extensions/nautilus-icon-canvas-item.c b/libnautilus-extensions/nautilus-icon-canvas-item.c index 2e9c11bb6..58b729a95 100644 --- a/libnautilus-extensions/nautilus-icon-canvas-item.c +++ b/libnautilus-extensions/nautilus-icon-canvas-item.c @@ -3,7 +3,6 @@ /* Nautilus - Icon canvas item class for icon container. * * Copyright (C) 2000 Eazel, Inc - * * Author: Andy Hertzfeld <andy@eazel.com> * * This library is free software; you can redistribute it and/or @@ -29,8 +28,10 @@ #include <string.h> #include <stdio.h> #include <gtk/gtksignal.h> +#include <gtk/gtkmain.h> #include <gdk-pixbuf/gdk-pixbuf.h> #include <libgnome/gnome-i18n.h> +#include <libgnomeui/gnome-canvas-rect-ellipse.h> #include <libgnomeui/gnome-canvas-util.h> #include <libgnomeui/gnome-icon-text.h> #include <libart_lgpl/art_rgb.h> @@ -38,6 +39,7 @@ #include <libart_lgpl/art_rgb_rgba_affine.h> #include <libart_lgpl/art_svp_vpath.h> #include "nautilus-icon-private.h" +#include "nautilus-canvas-note-item.h" #include <eel/eel-string.h> #include <eel/eel-art-extensions.h> #include <eel/eel-glib-extensions.h> @@ -54,9 +56,6 @@ #include <eel/eel-smooth-text-layout.h> #include <eel/eel-smooth-text-layout-cache.h> -/* Comment this out if the new smooth fonts code give you problems - * This isnt meant to be permanent. Its just a precaution. - */ #define EMBLEM_SPACING 2 /* gap between bottom of icon and start of text box */ @@ -80,6 +79,17 @@ struct NautilusIconCanvasItemDetails { GdkFont *font; NautilusEmblemAttachPoints *attach_points; + /* stuff for controls; if this gets too big, we'll put it in a separate struct */ + GtkWidget *control; /* optional Bonobo control*/ + guint control_destroy_id; + + /* stuff for annotations - since these are infrequently used, we probably should + * combine them into a structure so we only use a pointer for every item eventually + */ + GnomeCanvasItem *annotation; + int annotation_time_out; + int note_state; + /* Size of the text at current font. */ int text_width; int text_height; @@ -93,7 +103,7 @@ struct NautilusIconCanvasItemDetails { guint is_highlighted_for_drop : 1; guint show_stretch_handles : 1; guint is_prelit : 1; - + guint in_control_destroy : 1; gboolean is_renaming; /* Font stuff whilst in smooth mode */ @@ -112,7 +122,7 @@ enum { ARG_EDITABLE_TEXT, ARG_ADDITIONAL_TEXT, ARG_FONT, - ARG_HIGHLIGHTED_FOR_SELECTION, + ARG_HIGHLIGHTED_FOR_SELECTION, ARG_HIGHLIGHTED_AS_KEYBOARD_FOCUS, ARG_HIGHLIGHTED_FOR_DROP, ARG_MODIFIER, @@ -210,9 +220,13 @@ static void get_icon_canvas_rectangle (NautilusIconCanvasIt static void emblem_layout_reset (EmblemLayout *layout, NautilusIconCanvasItem *icon_item, const ArtIRect *icon_rect); -static gboolean emblem_layout_next (EmblemLayout *layout, +static gboolean emblem_layout_next (EmblemLayout *layout, GdkPixbuf **emblem_pixbuf, ArtIRect *emblem_rect); +static void get_emblem_rectangle (NautilusIconCanvasItem *icon_item, + int which_emblem, + ArtIRect *rect); + static void draw_pixbuf (GdkPixbuf *pixbuf, GdkDrawable *drawable, int x, @@ -222,7 +236,6 @@ static gboolean hit_test_stretch_handle (NautilusIconCanvasIt static gboolean icon_canvas_item_is_smooth (const NautilusIconCanvasItem *icon_item); - EEL_DEFINE_CLASS_BOILERPLATE (NautilusIconCanvasItem, nautilus_icon_canvas_item, GNOME_TYPE_CANVAS_ITEM) static EelSmoothTextLayoutCache *layout_cache; @@ -308,7 +321,8 @@ nautilus_icon_canvas_item_initialize (NautilusIconCanvasItem *icon_item) /* set up the default font and size */ icon_item->details->smooth_font_size = 12; - icon_item->details->smooth_font = eel_scalable_font_get_default_font (); + icon_item->details->smooth_font = eel_scalable_font_get_default_font (); + icon_item->details->annotation_time_out = -1; } /* Destroy handler for the icon canvas item. */ @@ -339,6 +353,11 @@ nautilus_icon_canvas_item_destroy (GtkObject *object) gdk_font_unref (details->font); } + if (details->control && !details->in_control_destroy) { + gtk_signal_disconnect (GTK_OBJECT (details->control), details->control_destroy_id); + gtk_widget_destroy (details->control); + } + gtk_object_unref (GTK_OBJECT (icon_item->details->smooth_font)); icon_item->details->smooth_font = NULL; @@ -370,6 +389,43 @@ nautilus_icon_canvas_item_invalidate_label_size (NautilusIconCanvasItem *item) item->details->text_height = -1; } +/* abstraction layer for icon width and height, to separate it from pixbuf with and height */ +static int +nautilus_icon_canvas_item_get_icon_width (NautilusIconCanvasItem *item) +{ + GtkRequisition size_requisition; + double scale_factor = GNOME_CANVAS_ITEM (item)->canvas->pixels_per_unit; + + if (item->details->control != NULL) { + gtk_widget_size_request (item->details->control, &size_requisition); + return size_requisition.width * scale_factor; + } + + if (item->details->pixbuf == NULL) { + return NAUTILUS_ICON_SIZE_STANDARD; + } + + return gdk_pixbuf_get_width (item->details->pixbuf); +} + +static int +nautilus_icon_canvas_item_get_icon_height (NautilusIconCanvasItem *item) +{ + GtkRequisition size_requisition; + double scale_factor = GNOME_CANVAS_ITEM (item)->canvas->pixels_per_unit; + + if (item->details->control != NULL) { + gtk_widget_size_request (item->details->control, &size_requisition); + return size_requisition.height * scale_factor; + } + if (item->details->pixbuf == NULL) { + return NAUTILUS_ICON_SIZE_STANDARD; + } + + return gdk_pixbuf_get_height (item->details->pixbuf); +} + + /* Set_arg handler for the icon item. */ static void nautilus_icon_canvas_item_set_arg (GtkObject *object, GtkArg *arg, guint arg_id) @@ -452,8 +508,8 @@ nautilus_icon_canvas_item_set_arg (GtkObject *object, GtkArg *arg, guint arg_id) case ARG_SMOOTH_FONT_SIZE: nautilus_icon_canvas_item_set_smooth_font_size (NAUTILUS_ICON_CANVAS_ITEM (object), GTK_VALUE_INT (*arg)); - break; - + break; + default: g_warning ("nautilus_icons_view_item_item_set_arg on unknown argument"); return; @@ -462,12 +518,27 @@ nautilus_icon_canvas_item_set_arg (GtkObject *object, GtkArg *arg, guint arg_id) gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (object)); } +/* handler for the control's destroy signal */ +static void +do_control_destroy (GtkObject *object, gpointer data) +{ + NautilusIconCanvasItemDetails *details; + + details = NAUTILUS_ICON_CANVAS_ITEM (data)->details; + + details->in_control_destroy = TRUE; + + gtk_object_destroy (GTK_OBJECT (data)); +} + /* Get_arg handler for the icon item */ static void nautilus_icon_canvas_item_get_arg (GtkObject *object, GtkArg *arg, guint arg_id) { NautilusIconCanvasItemDetails *details; + GnomeCanvasItem *item; + item = GNOME_CANVAS_ITEM (object); details = NAUTILUS_ICON_CANVAS_ITEM (object)->details; switch (arg_id) { @@ -515,12 +586,27 @@ GdkPixbuf * nautilus_icon_canvas_item_get_image (NautilusIconCanvasItem *item) { NautilusIconCanvasItemDetails *details; - + int width, height; + GdkPixbuf *pixbuf; + g_return_val_if_fail (NAUTILUS_IS_ICON_CANVAS_ITEM (item), NULL); details = item->details; - return details->pixbuf; + if (details->control) { + width = details->control->allocation.width; + height = details->control->allocation.height; + pixbuf = eel_gdk_pixbuf_get_from_window_safe (details->control->window, + details->control->allocation.x, + details->control->allocation.y, + details->control->allocation.width, + details->control->allocation.height); + } else { + pixbuf = details->pixbuf; + gdk_pixbuf_ref (pixbuf); + } + + return pixbuf; } void @@ -622,6 +708,12 @@ recompute_bounding_box (NautilusIconCanvasItem *icon_item) item->y1 = top_left.y; item->x2 = bottom_right.x; item->y2 = bottom_right.y; + + if (icon_item->details->control) + gtk_layout_move (GTK_LAYOUT (item->canvas), icon_item->details->control, + item->x1 + item->canvas->zoom_xofs, + item->y1 + item->canvas->zoom_yofs); + } static void @@ -636,13 +728,16 @@ compute_text_rectangle (NautilusIconCanvasItem *item, text_rect->y1 = text_rect->y0 + item->details->text_height; } + void nautilus_icon_canvas_item_update_bounds (NautilusIconCanvasItem *item) { ArtIRect before, after, emblem_rect; EmblemLayout emblem_layout; GdkPixbuf *emblem_pixbuf; - + GtkRequisition size_requisition; + int item_width, item_height; + /* Compute new bounds. */ eel_gnome_canvas_item_get_current_canvas_bounds (GNOME_CANVAS_ITEM (item), &before); @@ -669,6 +764,16 @@ nautilus_icon_canvas_item_update_bounds (NautilusIconCanvasItem *item) art_irect_union (&item->details->emblem_rect, &item->details->emblem_rect, &emblem_rect); } + /* if there is an embedded control, make a size request and size accordingly */ + if (item->details->control) { + /* size the control appropriately */ + gtk_widget_size_request (item->details->control, &size_requisition); + item_width = size_requisition.width * GNOME_CANVAS_ITEM (item)->canvas->pixels_per_unit; + item_height = size_requisition.height * GNOME_CANVAS_ITEM (item)->canvas->pixels_per_unit; + + gtk_widget_set_usize (item->details->control, item_width, item_height); + } + /* Send out the bounds_changed signal and queue a redraw. */ eel_gnome_canvas_request_redraw_rectangle (GNOME_CANVAS_ITEM (item)->canvas, &before); @@ -809,7 +914,7 @@ draw_or_measure_label_text (NautilusIconCanvasItem *item, canvas_item = GNOME_CANVAS_ITEM (item); if (drawable != NULL) { - icon_width = details->pixbuf == NULL ? 0 : gdk_pixbuf_get_width (details->pixbuf); + icon_width = details->pixbuf == NULL ? 0 : nautilus_icon_canvas_item_get_icon_width (item); gc = gdk_gc_new (canvas_item->canvas->layout.bin_window); gdk_gc_get_values (gc, &save_gc); } @@ -1125,6 +1230,7 @@ emblem_layout_next (EmblemLayout *layout, /* Advance to the next emblem. */ layout->emblem = layout->emblem->next; + layout->index += 1; attach_points = layout->icon_item->details->attach_points; if (attach_points != NULL) { @@ -1135,8 +1241,6 @@ emblem_layout_next (EmblemLayout *layout, x = layout->icon_rect.x0 + attach_points->points[layout->index].x; y = layout->icon_rect.y0 + attach_points->points[layout->index].y; - layout->index += 1; - /* Return the rectangle and pixbuf. */ *emblem_pixbuf = pixbuf; emblem_rect->x0 = x - width / 2; @@ -1211,11 +1315,17 @@ emblem_layout_next (EmblemLayout *layout, /* Return the rectangle and pixbuf. */ *emblem_pixbuf = pixbuf; - emblem_rect->x0 = x - width / 2; - emblem_rect->y0 = y - height / 2; + if (layout->icon_item->details->control) { + emblem_rect->x0 = x; + emblem_rect->y0 = y; + } else { + emblem_rect->x0 = x - width / 2; + emblem_rect->y0 = y - height / 2; + } + emblem_rect->x1 = emblem_rect->x0 + width; emblem_rect->y1 = emblem_rect->y0 + height; - + return TRUE; } @@ -1330,10 +1440,6 @@ nautilus_icon_canvas_item_draw (GnomeCanvasItem *item, GdkDrawable *drawable, icon_item = NAUTILUS_ICON_CANVAS_ITEM (item); details = icon_item->details; - /* Draw the pixbuf. */ - if (details->pixbuf == NULL) { - return; - } /* Compute icon rectangle in drawable coordinates. */ icon_rect = icon_item->details->canvas_rect; @@ -1341,12 +1447,29 @@ nautilus_icon_canvas_item_draw (GnomeCanvasItem *item, GdkDrawable *drawable, icon_rect.y0 -= y; icon_rect.x1 -= x; icon_rect.y1 -= y; + /* draw the icon or widget */ + if (icon_item->details->control) { + gtk_widget_queue_draw (icon_item->details->control); + } else { + if (details->pixbuf != NULL) { + + /* Compute icon rectangle in drawable coordinates. */ + get_icon_canvas_rectangle (icon_item, &icon_rect); + icon_rect.x0 -= x; + icon_rect.y0 -= y; + icon_rect.x1 -= x; + icon_rect.y1 -= y; + + /* if the pre-lit or selection flag is set, make a pre-lit or darkened pixbuf and draw that instead */ + temp_pixbuf = map_pixbuf (icon_item); + draw_pixbuf (temp_pixbuf, drawable, icon_rect.x0, icon_rect.y0); + + if (temp_pixbuf != details->pixbuf) { + gdk_pixbuf_unref (temp_pixbuf); + } + + } - /* if the pre-lit or selection flag is set, make a pre-lit or darkened pixbuf and draw that instead */ - temp_pixbuf = map_pixbuf (icon_item); - draw_pixbuf (temp_pixbuf, drawable, icon_rect.x0, icon_rect.y0); - if (temp_pixbuf != details->pixbuf) { - gdk_pixbuf_unref (temp_pixbuf); } /* Draw the emblem pixbufs. */ @@ -1431,7 +1554,7 @@ draw_or_measure_label_text_aa (NautilusIconCanvasItem *item, if (destination_pixbuf == NULL ) { icon_width = 0; } else { - icon_width = details->pixbuf == NULL ? 0 : gdk_pixbuf_get_width (details->pixbuf); + icon_width = details->pixbuf == NULL ? 0 : nautilus_icon_canvas_item_get_icon_width (item); } max_text_width = floor (nautilus_icon_canvas_item_get_max_text_width (item)); @@ -1706,15 +1829,21 @@ nautilus_icon_canvas_item_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf) gnome_canvas_buf_ensure_buf (buf); buf->is_bg = FALSE; } - - /* draw the icon */ - eel_gnome_canvas_draw_pixbuf (buf, temp_pixbuf, icon_rect.x0, icon_rect.y0); + + /* draw the icon or widget */ + if (icon_item->details->control) { + gtk_widget_queue_draw (icon_item->details->control); + } else { + eel_gnome_canvas_draw_pixbuf (buf, temp_pixbuf, icon_rect.x0, icon_rect.y0); + } if (temp_pixbuf != icon_item->details->pixbuf) { gdk_pixbuf_unref (temp_pixbuf); } - - /* draw the emblems */ + + /* draw the emblems */ + get_icon_canvas_rectangle (icon_item, &icon_rect); + emblem_layout_reset (&emblem_layout, icon_item, &icon_rect); while (emblem_layout_next (&emblem_layout, &emblem_pixbuf, &emblem_rect)) { eel_gnome_canvas_draw_pixbuf (buf, emblem_pixbuf, emblem_rect.x0, emblem_rect.y0); @@ -1728,6 +1857,124 @@ nautilus_icon_canvas_item_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf) draw_label_text_aa (icon_item, buf, icon_rect.x0, icon_rect.y1, x_delta); } +/* create an annotation for the emblem designated by the passed-in index */ +static void +create_annotation (NautilusIconCanvasItem *icon_item, int emblem_index) +{ + uint fill_color, outline_color; + double top, left; + double right, bottom; + ArtDRect icon_rect; + ArtIRect emblem_rect; + int emblem_x, emblem_y; + double world_emblem_x, world_emblem_y; + int annotation_width; + char *note_text; + GnomeCanvas *canvas; + GnomeCanvasItem *item; + + /* compute the position for the top left of the annotation */ + nautilus_icon_canvas_item_get_icon_rectangle (icon_item, &icon_rect); + left = icon_rect.x0 + 8.0; + top = icon_rect.y0 + 8.0; + + fill_color = 0xFFFF75E0; + outline_color = 0x000000FF; + + canvas = GNOME_CANVAS_ITEM (icon_item)->canvas; + item = GNOME_CANVAS_ITEM (icon_item); + + note_text = nautilus_icon_container_get_note_text (NAUTILUS_ICON_CONTAINER (canvas), icon_item->user_data, emblem_index); + + icon_item->details->annotation = gnome_canvas_item_new + (gnome_canvas_root (canvas), + nautilus_canvas_note_item_get_type (), + "x1", left, + "y1", top, + "fill_color_rgba", fill_color, + "outline_color_rgba", outline_color, + "note_text", note_text, + "width_pixels", 1, + NULL); + + g_free (note_text); + + /* reposition the item now that it's had a chance to be properly sized */ + if (canvas->aa) { + get_emblem_rectangle (icon_item, emblem_index, &emblem_rect); + annotation_width = icon_item->details->annotation->x2 - icon_item->details->annotation->x1; + + emblem_x = (emblem_rect.x0 + emblem_rect.x1) / 2; + emblem_y = (emblem_rect.y0 + emblem_rect.y1) / 2; + gnome_canvas_c2w (canvas, emblem_x, emblem_y, &world_emblem_x, &world_emblem_y); + + left = world_emblem_x - (annotation_width / 2.0 ); + top = world_emblem_y; + right = left + annotation_width; + bottom = top + icon_item->details->annotation->y2 - icon_item->details->annotation->y1; + + gnome_canvas_item_set (icon_item->details->annotation, "x1", left, "y1", top, "x2", right, "y2", bottom, NULL); + } + + gnome_canvas_item_raise_to_top (icon_item->details->annotation); +} + +/* remove any annotation that's showing */ +static void +remove_annotation (NautilusIconCanvasItem *icon_item) +{ + if (icon_item->details->annotation != NULL) { + gtk_object_destroy (GTK_OBJECT (icon_item->details->annotation)); + icon_item->details->annotation = NULL; + icon_item->details->note_state = 0; + } +} + +/* handle the timeout firing by creating the annotation */ +static int +create_annotation_timeout_callback (gpointer callback_data) +{ + NautilusIconCanvasItem *icon_item; + + icon_item = NAUTILUS_ICON_CANVAS_ITEM (callback_data); + create_annotation (icon_item, icon_item->details->note_state); + + icon_item->details->annotation_time_out = -1; + return 0; +} + +/* manage showing and hiding annotations, based on mouse-over the passed-in emblem */ +static void +nautilus_icon_canvas_item_set_note_state (NautilusIconCanvasItem *icon_item, int new_state) +{ + /* nothing to do if nothing changed */ + if (new_state == icon_item->details->note_state) { + return; + } + + /* if there already is a timeout in progress and we're showing one, just wait for it to fire */ + if (new_state > 0 && icon_item->details->annotation_time_out >= 0) { + return; + } + + /* get rid of the old annotation, if there was one */ + if (icon_item->details->annotation) { + remove_annotation (icon_item); + } + + if (icon_item->details->annotation_time_out >= 0) { + gtk_timeout_remove (icon_item->details->annotation_time_out); + icon_item->details->annotation_time_out = -1; + } + + icon_item->details->note_state = new_state; + + /* add a timeout to create the new annotation */ + if (new_state > 0) { + icon_item->details->annotation_time_out = gtk_timeout_add (750, create_annotation_timeout_callback, icon_item); + } +} + /* handle events */ @@ -1735,7 +1982,12 @@ static int nautilus_icon_canvas_item_event (GnomeCanvasItem *item, GdkEvent *event) { NautilusIconCanvasItem *icon_item; - + GdkEventMotion *motion_event; + ArtIRect hit_rect; + ArtDRect world_rect; + HitType hit_type; + int hit_index, emblem_state; + icon_item = NAUTILUS_ICON_CANVAS_ITEM (item); switch (event->type) { @@ -1782,10 +2034,29 @@ nautilus_icon_canvas_item_event (GnomeCanvasItem *item, GdkEvent *event) icon_item->details->is_prelit = FALSE; icon_item->details->is_active = 0; icon_item->details->is_highlighted_for_drop = FALSE; + + nautilus_icon_canvas_item_set_note_state (icon_item, 0); gnome_canvas_item_request_update (item); } return TRUE; + + case GDK_MOTION_NOTIFY: + motion_event = (GdkEventMotion*) event; + + world_rect.x0 = motion_event->x; + world_rect.y0 = motion_event->y; + world_rect.x1 = world_rect.x0 + 1.0; + world_rect.y1 = world_rect.y0 + 1.0; + + eel_gnome_canvas_world_to_canvas_rectangle + (GNOME_CANVAS_ITEM (item)->canvas, &world_rect, &hit_rect); + /* hit-test so we can handle tooltips for emblems */ + nautilus_icon_canvas_item_hit_test_full (icon_item, &hit_rect, &hit_type, &hit_index); + emblem_state = hit_type == EMBLEM_HIT ? hit_index : 0; + nautilus_icon_canvas_item_set_note_state (icon_item, emblem_state); + return TRUE; + default: /* Don't eat up other events; icon container might use them. */ return FALSE; @@ -1838,10 +2109,14 @@ hit_test_pixbuf (GdkPixbuf *pixbuf, const ArtIRect *pixbuf_location, const ArtIR return FALSE; } -static gboolean -hit_test (NautilusIconCanvasItem *icon_item, const ArtIRect *canvas_rect) +gboolean +nautilus_icon_canvas_item_hit_test_full (NautilusIconCanvasItem *icon_item, + const ArtIRect *canvas_rect, + HitType *hit_type, + int *hit_index) { NautilusIconCanvasItemDetails *details; + ArtIRect icon_rect; ArtIRect emblem_rect; EmblemLayout emblem_layout; GdkPixbuf *emblem_pixbuf; @@ -1855,12 +2130,40 @@ hit_test (NautilusIconCanvasItem *icon_item, const ArtIRect *canvas_rect) return FALSE; } + /* default to -1, which means nothing was hit */ + if (hit_index != NULL) { + *hit_index = -1; + } + /* Check for hits in the stretch handles. */ if (hit_test_stretch_handle (icon_item, canvas_rect)) { + if (hit_type != NULL) { + *hit_type = STRETCH_HANDLE_HIT; + } return TRUE; } /* Check for hit in the icon. If we're highlighted for dropping, anywhere in the rect is OK */ + get_icon_canvas_rectangle (icon_item, &icon_rect); + + /* Check for hit in the emblem pixbufs first, since they appear on top of the icon. */ + emblem_layout_reset (&emblem_layout, icon_item, &icon_item->details->canvas_rect); + while (emblem_layout_next (&emblem_layout, &emblem_pixbuf, &emblem_rect)) { + if (hit_test_pixbuf (emblem_pixbuf, &emblem_rect, canvas_rect)) { + if (hit_type != NULL) { + *hit_type = EMBLEM_HIT; + } + if (hit_index != NULL) { + *hit_index = emblem_layout.index; + } + return TRUE; + } + } + + if (hit_type != NULL) { + *hit_type = ICON_HIT; + } + if (icon_item->details->is_highlighted_for_drop) { if (eel_art_irect_hits_irect (&icon_item->details->canvas_rect, canvas_rect)) { return TRUE; @@ -1874,17 +2177,17 @@ hit_test (NautilusIconCanvasItem *icon_item, const ArtIRect *canvas_rect) /* Check for hit in the text. */ if (eel_art_irect_hits_irect (&details->text_rect, canvas_rect) && !icon_item->details->is_renaming) { + if (hit_type != NULL) { + *hit_type = LABEL_HIT; + } return TRUE; } - /* Check for hit in the emblem pixbufs. */ - emblem_layout_reset (&emblem_layout, icon_item, &icon_item->details->canvas_rect); - while (emblem_layout_next (&emblem_layout, &emblem_pixbuf, &emblem_rect)) { - if (hit_test_pixbuf (emblem_pixbuf, &emblem_rect, canvas_rect)) { - return TRUE; - } + /* there wasn't a hit, so indicate that */ + if (hit_type != NULL) { + *hit_type = NO_HIT; } - + return FALSE; } @@ -1900,7 +2203,7 @@ nautilus_icon_canvas_item_point (GnomeCanvasItem *item, double x, double y, int canvas_rect.y0 = cy; canvas_rect.x1 = cx + 1; canvas_rect.y1 = cy + 1; - if (hit_test (NAUTILUS_ICON_CANVAS_ITEM (item), &canvas_rect)) { + if (nautilus_icon_canvas_item_hit_test_rectangle (NAUTILUS_ICON_CANVAS_ITEM (item), &canvas_rect)) { return 0.0; } else { /* This value means not hit. @@ -1939,8 +2242,8 @@ nautilus_icon_canvas_item_bounds (GnomeCanvasItem *item, icon_rect.x1 = 0; icon_rect.y1 = 0; } else { - icon_rect.x1 = gdk_pixbuf_get_width (details->pixbuf); - icon_rect.y1 = gdk_pixbuf_get_height (details->pixbuf); + icon_rect.x1 = nautilus_icon_canvas_item_get_icon_width (icon_item); + icon_rect.y1 = nautilus_icon_canvas_item_get_icon_height (icon_item); } /* Compute text rectangle. */ @@ -1986,10 +2289,32 @@ nautilus_icon_canvas_item_get_icon_rectangle (NautilusIconCanvasItem *item, pixbuf = item->details->pixbuf; pixels_per_unit = GNOME_CANVAS_ITEM (item)->canvas->pixels_per_unit; - rect->x1 = rect->x0 + (pixbuf == NULL ? 0 : gdk_pixbuf_get_width (pixbuf)) / pixels_per_unit; - rect->y1 = rect->y0 + (pixbuf == NULL ? 0 : gdk_pixbuf_get_height (pixbuf)) / pixels_per_unit; + rect->x1 = rect->x0 + (pixbuf == NULL ? 0 : nautilus_icon_canvas_item_get_icon_width (item)) / pixels_per_unit; + rect->y1 = rect->y0 + (pixbuf == NULL ? 0 : nautilus_icon_canvas_item_get_icon_height (item)) / pixels_per_unit; } +static void +get_emblem_rectangle (NautilusIconCanvasItem *icon_item, + int which_emblem, + ArtIRect *rect) +{ + EmblemLayout emblem_layout; + GdkPixbuf *pixbuf; + int emblem_index; + + emblem_layout_reset (&emblem_layout, icon_item, &icon_item->details->canvas_rect); + emblem_index = 0; + + rect->x0 = 0; + rect->y0 = 0; + rect->x1 = 0; + rect->y1 = 0; + + while (emblem_index < which_emblem && emblem_layout_next (&emblem_layout, &pixbuf, rect)) { + emblem_index += 1; + } +} + /* Get the rectangle of the icon only, in canvas coordinates. */ static void get_icon_canvas_rectangle (NautilusIconCanvasItem *item, @@ -2013,8 +2338,8 @@ get_icon_canvas_rectangle (NautilusIconCanvasItem *item, pixbuf = item->details->pixbuf; - rect->x1 = rect->x0 + (pixbuf == NULL ? 0 : gdk_pixbuf_get_width (pixbuf)); - rect->y1 = rect->y0 + (pixbuf == NULL ? 0 : gdk_pixbuf_get_height (pixbuf)); + rect->x1 = rect->x0 + (pixbuf == NULL ? 0 : nautilus_icon_canvas_item_get_icon_width (item)); + rect->y1 = rect->y0 + (pixbuf == NULL ? 0 : nautilus_icon_canvas_item_get_icon_height (item)); } void @@ -2110,10 +2435,11 @@ nautilus_icon_canvas_item_hit_test_stretch_handles (NautilusIconCanvasItem *item gboolean nautilus_icon_canvas_item_hit_test_rectangle (NautilusIconCanvasItem *item, const ArtIRect *canvas_rect) { + g_return_val_if_fail (NAUTILUS_IS_ICON_CANVAS_ITEM (item), FALSE); g_return_val_if_fail (canvas_rect != NULL, FALSE); - return hit_test (item, canvas_rect); + return nautilus_icon_canvas_item_hit_test_full (item, canvas_rect, NULL, NULL); } const char * @@ -2171,6 +2497,41 @@ nautilus_icon_canvas_item_set_smooth_font (NautilusIconCanvasItem *icon_item, } } +GtkWidget * +nautilus_icon_canvas_item_get_control (NautilusIconCanvasItem *icon_item) +{ + return icon_item->details->control; +} + +void +nautilus_icon_canvas_item_set_control (NautilusIconCanvasItem *icon_item, GtkWidget *control) +{ + GnomeCanvasItem *item; + + if (icon_item->details->control == control) { + return; + } + + item = GNOME_CANVAS_ITEM (icon_item); + if (icon_item->details->control) { + gtk_signal_disconnect (GTK_OBJECT (icon_item->details->control), icon_item->details->control_destroy_id); + gtk_container_remove (GTK_CONTAINER (item->canvas), icon_item->details->control); + icon_item->details->control = NULL; + } + + if (control) { + icon_item->details->control = control; + icon_item->details->control_destroy_id = gtk_signal_connect (GTK_OBJECT (control), + "destroy", + (GtkSignalFunc) do_control_destroy, + item); + gtk_widget_show (control); + gtk_layout_put (GTK_LAYOUT (item->canvas), control, + item->x1 + item->canvas->zoom_xofs, + item->y1 + item->canvas->zoom_yofs); + } +} + void nautilus_icon_canvas_item_set_smooth_font_size (NautilusIconCanvasItem *icon_item, int font_size) diff --git a/libnautilus-extensions/nautilus-icon-canvas-item.h b/libnautilus-extensions/nautilus-icon-canvas-item.h index f7be68e70..2f9f90a34 100644 --- a/libnautilus-extensions/nautilus-icon-canvas-item.h +++ b/libnautilus-extensions/nautilus-icon-canvas-item.h @@ -44,6 +44,14 @@ BEGIN_GNOME_DECLS #define NAUTILUS_IS_ICON_CANVAS_ITEM_CLASS(klass) \ (GTK_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_ICON_CANVAS_ITEM)) +typedef enum { + NO_HIT, + ICON_HIT, + LABEL_HIT, + STRETCH_HANDLE_HIT, + EMBLEM_HIT +} HitType; + typedef struct NautilusIconCanvasItem NautilusIconCanvasItem; typedef struct NautilusIconCanvasItemClass NautilusIconCanvasItemClass; typedef struct NautilusIconCanvasItemDetails NautilusIconCanvasItemDetails; @@ -80,10 +88,19 @@ const char *nautilus_icon_canvas_item_get_editable_text (NautilusIconCanv void nautilus_icon_canvas_item_set_renaming (NautilusIconCanvasItem *icon_item, gboolean state); +GtkWidget * nautilus_icon_canvas_item_get_control (NautilusIconCanvasItem *icon_item); +void nautilus_icon_canvas_item_set_control (NautilusIconCanvasItem *icon_item, + GtkWidget *control); + /* geometry and hit testing */ gboolean nautilus_icon_canvas_item_hit_test_rectangle (NautilusIconCanvasItem *item, const ArtIRect *canvas_rect); +gboolean nautilus_icon_canvas_item_hit_test_full (NautilusIconCanvasItem *icon_item, + const ArtIRect *canvas_rect, + HitType *hit_type, + int *hit_index); + gboolean nautilus_icon_canvas_item_hit_test_stretch_handles (NautilusIconCanvasItem *item, const ArtPoint *world_point); void nautilus_icon_canvas_item_invalidate_label_size (NautilusIconCanvasItem *item); diff --git a/libnautilus-extensions/nautilus-icon-container.c b/libnautilus-extensions/nautilus-icon-container.c index 9daecc782..e41f03522 100644 --- a/libnautilus-extensions/nautilus-icon-container.c +++ b/libnautilus-extensions/nautilus-icon-container.c @@ -119,10 +119,12 @@ enum { */ }; -static void activate_selected_items (NautilusIconContainer *container); +static void activate_selected_items (NautilusIconContainer *container, + int select_location); static void nautilus_icon_container_initialize_class (NautilusIconContainerClass *class); static void nautilus_icon_container_initialize (NautilusIconContainer *container); static void nautilus_icon_container_theme_changed (gpointer user_data); +static void nautilus_icon_container_annotation_changed (gpointer user_data); static void compute_stretch (StretchState *start, StretchState *current); @@ -166,10 +168,12 @@ enum { CONTEXT_CLICK_SELECTION, MIDDLE_CLICK, GET_CONTAINER_URI, + GET_ICON_CONTROL, GET_ICON_IMAGES, GET_ICON_TEXT, GET_ICON_URI, GET_ICON_DROP_TARGET_URI, + GET_ICON_ANNOTATION, GET_STORED_ICON_POSITION, ICON_POSITION_CHANGED, ICON_TEXT_CHANGED, @@ -2286,7 +2290,6 @@ select_previous_or_next_name (NautilusIconContainer *container, } /* GtkObject methods. */ - static void destroy (GtkObject *object) { @@ -2333,6 +2336,10 @@ destroy (GtkObject *object) gtk_object_destroy (GTK_OBJECT (container->details->rename_widget)); } + /* remove the annotation preference callbacks */ + nautilus_preferences_remove_callback (NAUTILUS_PREFERENCES_LOOKUP_ANNOTATIONS, nautilus_icon_container_annotation_changed, container); + nautilus_preferences_remove_callback (NAUTILUS_PREFERENCES_DISPLAY_ANNOTATIONS, nautilus_icon_container_annotation_changed, container); + /* FIXME: The code to extract colors from the theme should be in FMDirectoryView, not here. * The NautilusIconContainer class should simply provide calls to set the colors. */ @@ -2530,13 +2537,42 @@ button_press_event (GtkWidget *widget, return return_value; } +/* utility routine to determine which portion of an icon was clicked, and return the + * emblem index if an emblem was clicked on + */ +static int +hit_test_item (NautilusIconCanvasItem *icon_item, GdkEventButton *event) +{ + ArtDRect world_rect; + ArtIRect canvas_rect; + HitType hit_type; + int emblem_index; + + gnome_canvas_window_to_world (GNOME_CANVAS_ITEM (icon_item)->canvas, event->x, event->y, + &world_rect.x0, &world_rect.y0); + world_rect.x1 = world_rect.x0 + 1.0; + world_rect.y1 = world_rect.y0 + 1.0; + + eel_gnome_canvas_world_to_canvas_rectangle + (GNOME_CANVAS_ITEM (icon_item)->canvas, &world_rect, &canvas_rect); + + if (nautilus_icon_canvas_item_hit_test_full (icon_item, &canvas_rect, &hit_type, &emblem_index)) { + if (hit_type == EMBLEM_HIT) { + return emblem_index; + } + } + return 0; +} + static void nautilus_icon_container_did_not_drag (NautilusIconContainer *container, GdkEventButton *event) { + int click_location; NautilusIconContainerDetails *details; + details = container->details; - + if (!button_event_modifies_selection (event) && !details->drag_icon->is_selected) { gboolean selection_changed; @@ -2565,7 +2601,8 @@ nautilus_icon_container_did_not_drag (NautilusIconContainer *container, * NautilusList goes the other way because its "links" seem * much more link-like. */ - activate_selected_items (container); + click_location = hit_test_item (details->drag_icon->item, event); + activate_selected_items (container, click_location); } } } @@ -2997,7 +3034,7 @@ key_press_event (GtkWidget *widget, break; case GDK_Return: case GDK_KP_Enter: - activate_selected_items (container); + activate_selected_items (container, 0); handled = TRUE; break; case GDK_Escape: @@ -3070,9 +3107,10 @@ nautilus_icon_container_initialize_class (NautilusIconContainerClass *class) object_class->type, GTK_SIGNAL_OFFSET (NautilusIconContainerClass, activate), - gtk_marshal_NONE__POINTER, - GTK_TYPE_NONE, 1, - GTK_TYPE_POINTER); + gtk_marshal_NONE__POINTER_INT, + GTK_TYPE_NONE, 2, + GTK_TYPE_POINTER, + GTK_TYPE_INT); signals[CONTEXT_CLICK_SELECTION] = gtk_signal_new ("context_click_selection", GTK_RUN_LAST, @@ -3147,6 +3185,16 @@ nautilus_icon_container_initialize_class (NautilusIconContainerClass *class) gtk_marshal_NONE__POINTER, GTK_TYPE_NONE, 1, GTK_TYPE_POINTER); + signals[GET_ICON_CONTROL] + = gtk_signal_new ("get_icon_control", + GTK_RUN_LAST, + object_class->type, + GTK_SIGNAL_OFFSET (NautilusIconContainerClass, + get_icon_control), + gtk_marshal_NONE__POINTER_POINTER, + GTK_TYPE_NONE, 2, + GTK_TYPE_POINTER, + GTK_TYPE_POINTER); signals[GET_ICON_IMAGES] = gtk_signal_new ("get_icon_images", GTK_RUN_LAST, @@ -3187,6 +3235,16 @@ nautilus_icon_container_initialize_class (NautilusIconContainerClass *class) eel_gtk_marshal_STRING__POINTER, GTK_TYPE_STRING, 1, GTK_TYPE_POINTER); + signals[GET_ICON_ANNOTATION] + = gtk_signal_new ("get_icon_annotation", + GTK_RUN_LAST, + object_class->type, + GTK_SIGNAL_OFFSET (NautilusIconContainerClass, + get_icon_annotation), + eel_gtk_marshal_STRING__POINTER_INT, + GTK_TYPE_STRING, 2, + GTK_TYPE_POINTER, + GTK_TYPE_INT); signals[COMPARE_ICONS] = gtk_signal_new ("compare_icons", GTK_RUN_LAST, @@ -3384,6 +3442,10 @@ nautilus_icon_container_initialize (NautilusIconContainer *container) gtk_signal_connect (GTK_OBJECT (container), "focus-out-event", handle_focus_out_event, NULL); + /* add callbacks to notify us when the annotation state changes */ + nautilus_preferences_add_callback (NAUTILUS_PREFERENCES_LOOKUP_ANNOTATIONS, nautilus_icon_container_annotation_changed, container); + nautilus_preferences_add_callback (NAUTILUS_PREFERENCES_DISPLAY_ANNOTATIONS, nautilus_icon_container_annotation_changed, container); + /* FIXME: The code to extract colors from the theme should be in FMDirectoryView, not here. * The NautilusIconContainer class should simply provide calls to set the colors. */ @@ -3519,7 +3581,7 @@ handle_icon_button_press (NautilusIconContainer *container, details->drag_button = 0; details->drag_icon = NULL; - activate_selected_items (container); + activate_selected_items (container, 0); } return TRUE; @@ -3674,7 +3736,7 @@ icon_destroy (NautilusIconContainer *container, /* activate any selected items in the container */ static void -activate_selected_items (NautilusIconContainer *container) +activate_selected_items (NautilusIconContainer *container, int select_location) { GList *selection; @@ -3684,7 +3746,8 @@ activate_selected_items (NautilusIconContainer *container) if (selection != NULL) { gtk_signal_emit (GTK_OBJECT (container), signals[ACTIVATE], - selection); + selection, + select_location); } g_list_free (selection); } @@ -3720,6 +3783,7 @@ nautilus_icon_container_update_icon (NautilusIconContainer *container, GdkPixbuf *pixbuf, *emblem_pixbuf, *saved_pixbuf; GList *emblem_scalable_icons, *emblem_pixbufs, *p; char *editable_text, *additional_text; + GtkWidget *embedded_control; GdkFont *font; int smooth_font_size; @@ -3754,8 +3818,7 @@ nautilus_icon_container_update_icon (NautilusIconContainer *container, eel_scalable_icon_unref (scalable_icon); - /* in the rare case an image is too small, scale it up */ - + /* in the rare case an image is too small, scale it up */ width = gdk_pixbuf_get_width (pixbuf); height = gdk_pixbuf_get_height (pixbuf); if (width < min_image_size || height < min_image_size) { @@ -3763,9 +3826,11 @@ nautilus_icon_container_update_icon (NautilusIconContainer *container, /* don't let it exceed the maximum width in the other dimension */ scale_factor = MIN (scale_factor, max_image_size / width); scale_factor = MIN (scale_factor, max_image_size / height); - + scaled_width = floor (width * scale_factor + .5); scaled_height = floor (height * scale_factor + .5); + + /* scale the image to the calculated size */ saved_pixbuf = pixbuf; pixbuf = gdk_pixbuf_scale_simple (pixbuf, scaled_width, scaled_height, GDK_INTERP_BILINEAR); gdk_pixbuf_unref (saved_pixbuf); @@ -3794,13 +3859,22 @@ nautilus_icon_container_update_icon (NautilusIconContainer *container, emblem_pixbufs = g_list_reverse (emblem_pixbufs); eel_scalable_icon_list_free (emblem_scalable_icons); + /* get the embedded control, if any */ + embedded_control = nautilus_icon_canvas_item_get_control (icon->item); + if (embedded_control == NULL) { + gtk_signal_emit (GTK_OBJECT (container), + signals[GET_ICON_CONTROL], + icon->data, + &embedded_control); + } + /* Get both editable and non-editable icon text */ gtk_signal_emit (GTK_OBJECT (container), signals[GET_ICON_TEXT], icon->data, &editable_text, &additional_text); - + /* If name of icon being renamed was changed from elsewhere, end renaming mode. * Alternatively, we could replace the characters in the editable text widget * with the new name, but that could cause timing problems if the user just @@ -3825,6 +3899,8 @@ nautilus_icon_container_update_icon (NautilusIconContainer *container, "smooth_font", details->smooth_label_font, NULL); + nautilus_icon_canvas_item_set_control (icon->item, embedded_control); + nautilus_icon_canvas_item_set_image (icon->item, pixbuf); nautilus_icon_canvas_item_set_attach_points (icon->item, &attach_points); nautilus_icon_canvas_item_set_emblems (icon->item, emblem_pixbufs); @@ -5073,6 +5149,33 @@ update_label_color (EelBackground *background, } } +/* handle the annotation preference changes by updating the icons */ +static void +nautilus_icon_container_annotation_changed (gpointer user_data) +{ + nautilus_icon_container_request_update_all (NAUTILUS_ICON_CONTAINER (user_data)); +} + + +/* handle fetching annotation info from the controller */ +char * +nautilus_icon_container_get_note_text (NautilusIconContainer *container, + gpointer user_data, + int emblem_index) +{ + NautilusIcon *icon; + char *note_text; + note_text = NULL; + + icon = (NautilusIcon*) user_data; + gtk_signal_emit (GTK_OBJECT (container), + signals[GET_ICON_ANNOTATION], + icon->data, + emblem_index, + ¬e_text); + + return note_text; +} /* Return if the icon container is a fixed size */ gboolean diff --git a/libnautilus-extensions/nautilus-icon-container.h b/libnautilus-extensions/nautilus-icon-container.h index 30382a19e..4510110ec 100644 --- a/libnautilus-extensions/nautilus-icon-container.h +++ b/libnautilus-extensions/nautilus-icon-container.h @@ -107,6 +107,10 @@ typedef struct { gboolean (* get_stored_icon_position) (NautilusIconContainer *container, NautilusIconData *data, NautilusIconPosition *position); + void + (* get_icon_control) (NautilusIconContainer *container, + NautilusIconData *data, + GtkWidget **control); NautilusScalableIcon * (* get_icon_images) (NautilusIconContainer *container, NautilusIconData *data, @@ -120,6 +124,9 @@ typedef struct { NautilusIconData *data); char * (* get_icon_drop_target_uri) (NautilusIconContainer *container, NautilusIconData *data); + char * (* get_icon_annotation) (NautilusIconContainer *container, + NautilusIconData *data, + int annotation_index); int (* compare_icons) (NautilusIconContainer *container, NautilusIconData *icon_a, NautilusIconData *icon_b); diff --git a/libnautilus-extensions/nautilus-icon-dnd.c b/libnautilus-extensions/nautilus-icon-dnd.c index 4d5364ea3..059051002 100644 --- a/libnautilus-extensions/nautilus-icon-dnd.c +++ b/libnautilus-extensions/nautilus-icon-dnd.c @@ -1279,11 +1279,12 @@ nautilus_icon_dnd_begin_drag (NautilusIconContainer *container, /* create a pixmap and mask to drag with */ - pixbuf = nautilus_icon_canvas_item_get_image (container->details->drag_icon->item); - + /* we want to drag semi-transparent pixbufs, but X is too slow dealing with stippled masks, so we had to remove the code; this comment is left as a memorial to it, with the hope that we get it back someday as X Windows improves */ + + pixbuf = nautilus_icon_canvas_item_get_image (container->details->drag_icon->item); /* compute the image's offset */ nautilus_icon_canvas_item_get_icon_rectangle diff --git a/libnautilus-extensions/nautilus-icon-private.h b/libnautilus-extensions/nautilus-icon-private.h index b1b73c147..d31a13265 100644 --- a/libnautilus-extensions/nautilus-icon-private.h +++ b/libnautilus-extensions/nautilus-icon-private.h @@ -254,4 +254,8 @@ void nautilus_icon_container_update_scroll_region (NautilusIconC guint32 nautilus_icon_container_get_label_color (NautilusIconContainer *container, gboolean first_line); +char * nautilus_icon_container_get_note_text (NautilusIconContainer *container, + gpointer user_data, + int emblem_index); + #endif /* NAUTILUS_ICON_CONTAINER_PRIVATE_H */ diff --git a/libnautilus-extensions/nautilus-link.c b/libnautilus-extensions/nautilus-link.c index 8fd7fcaba..76860d397 100644 --- a/libnautilus-extensions/nautilus-link.c +++ b/libnautilus-extensions/nautilus-link.c @@ -312,6 +312,35 @@ nautilus_link_local_get_additional_text (const char *path) (path, NAUTILUS_METADATA_KEY_EXTRA_TEXT); } +void nautilus_link_local_get_component_info (const char *path, + char **control_moniker, char **control_data) +{ + xmlDoc *document; + const char *mime_type; + + *control_moniker = NULL; + *control_data = NULL; + + /* Check mime type. Exit if it is not a nautilus link */ + mime_type = gnome_vfs_get_file_mime_type (path, NULL, FALSE); + if (strcmp (mime_type, "application/x-nautilus-link") != 0) { + return; + } + + document = xmlParseFile (path); + if (document != NULL) { + *control_moniker = xml_get_root_property (document, + NAUTILUS_METADATA_KEY_CONTROL_MONIKER); + + *control_data = xml_get_root_property (document, + NAUTILUS_METADATA_KEY_CONTROL_DATA); + + xmlFreeDoc (document); + } +} + + + /* utility to return the local pathname of a cached icon, given the leaf name */ /* if the icons directory hasn't been created yet, create it */ static char * diff --git a/libnautilus-extensions/nautilus-link.h b/libnautilus-extensions/nautilus-link.h index 88882ccb5..b9596eb7d 100644 --- a/libnautilus-extensions/nautilus-link.h +++ b/libnautilus-extensions/nautilus-link.h @@ -76,14 +76,21 @@ gboolean nautilus_link_local_set_link_uri (const char * none. Despite the fact that it takes a URI parameter, works only if * the file is local and does sync. I/O. */ -char * nautilus_link_local_get_additional_text (const char *path); +char * nautilus_link_local_get_additional_text (const char *path); /* Returns the image associated with a link file. Despite the fact * that it takes a URI parameter, works only if the file is local and * does sync. I/O on the link, although it does async. on the image * and caches if the image is remote. */ -char * nautilus_link_local_get_image_uri (const char *path); +char * nautilus_link_local_get_image_uri (const char *path); + +/* returns the moniker of the component associated with a link file, as well as configuration data. + * It works only if the file is local and does sync. I/O. + */ +void nautilus_link_local_get_component_info (const char *path, + char **control_moniker, + char **control_data); /* Returns the link type of a link file. * Works only if the file is local and does sync. I/O diff --git a/libnautilus-extensions/nautilus-metadata.h b/libnautilus-extensions/nautilus-metadata.h index 00f96a2da..70681853f 100644 --- a/libnautilus-extensions/nautilus-metadata.h +++ b/libnautilus-extensions/nautilus-metadata.h @@ -72,6 +72,12 @@ #define NAUTILUS_METADATA_KEY_ICON_SCALE "icon_scale" #define NAUTILUS_METADATA_KEY_CUSTOM_ICON "custom_icon" +#define NAUTILUS_METADATA_KEY_FILE_DIGEST "digest" +#define NAUTILUS_METADATA_KEY_NOTES_INFO "notes_info" + +#define NAUTILUS_METADATA_KEY_CONTROL_MONIKER "control_moniker" +#define NAUTILUS_METADATA_KEY_CONTROL_DATA "control_data" + /* per link file */ #define NAUTILUS_METADATA_KEY_EXTRA_TEXT "extra_text" diff --git a/libnautilus-private/Makefile.am b/libnautilus-private/Makefile.am index c0094f54d..515fdce88 100644 --- a/libnautilus-private/Makefile.am +++ b/libnautilus-private/Makefile.am @@ -38,6 +38,7 @@ endif libnautilus_extensions_la_LDFLAGS = \ $(dependency_static_libs) \ + $(AMMONITE_LIBS) \ $(BONOBO_PRINT_LIBS) \ $(BONOBOX_LIBS) \ $(ESD_LIBS) \ @@ -63,9 +64,11 @@ nautilus_metafile_server_idl_sources = \ libnautilus_extensions_la_SOURCES = \ $(nautilus_metafile_server_idl_sources) \ + nautilus-annotation.c \ nautilus-audio-player.c \ nautilus-bonobo-extensions.c \ nautilus-bookmark.c \ + nautilus-canvas-note-item.c \ nautilus-customization-data.c \ nautilus-dateedit-extensions.c \ nautilus-default-file-icon.c \ @@ -130,9 +133,11 @@ libnautilus_extensions_la_SOURCES = \ # Everything is private for now noinst_HEADERS = \ + nautilus-annotation.h \ nautilus-audio-player.h \ nautilus-bonobo-extensions.h \ nautilus-bookmark.h \ + nautilus-canvas-note-item.h \ nautilus-cdrom-extensions.h \ nautilus-customization-data.h \ nautilus-dateedit-extensions.h \ diff --git a/libnautilus-private/nautilus-annotation.c b/libnautilus-private/nautilus-annotation.c new file mode 100644 index 000000000..07a2d8512 --- /dev/null +++ b/libnautilus-private/nautilus-annotation.c @@ -0,0 +1,1312 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- + + nautilus-annotation.c: routines for getting and setting xml-based annotations associated + with the digest of a file. + + Copyright (C) 2001 Eazel, Inc. + + 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: Andy Hertzfeld <andy@eazel.com> +*/ + +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to md5_init, call md5_update as + * needed on buffers full of bytes, and then call md5_Final, which + * will fill a supplied 16-byte array with the digest. + */ + +/* parts of this file are : + * Written March 1993 by Branko Lankester + * Modified June 1993 by Colin Plumb for altered md5.c. + * Modified October 1995 by Erik Troan for RPM + */ + +#include <config.h> +#include "nautilus-annotation.h" + +#include "nautilus-file-utilities.h" +#include "nautilus-file.h" +#include "nautilus-file-private.h" +#include "nautilus-global-preferences.h" +#include "nautilus-metadata.h" +#include "nautilus-preferences.h" +#include <eel/eel-string.h> +#include <eel/eel-xml-extensions.h> +#include <eel/eel-vfs-extensions.h> +#include <ghttp.h> +#include <gnome-xml/parser.h> +#include <gnome-xml/xmlmemory.h> +#include <libgnome/gnome-util.h> +#include <libgnomevfs/gnome-vfs.h> +#include <libtrilobite/libammonite.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* icon selection callback function. */ +typedef void (* NautilusCalculateDigestCallback) (NautilusFile *file, char *file_digest); +typedef struct NautilusDigestFileHandle NautilusDigestFileHandle; + +typedef struct { + guint32 buf[4]; + guint32 bits[2]; + guchar in[64]; + int doByteReverse; +} MD5Context ; + +struct NautilusDigestFileHandle { + GnomeVFSAsyncHandle *handle; + NautilusCalculateDigestCallback callback; + NautilusFile *file; + char *buffer; + gboolean opened; + MD5Context digest_context; +}; + +#define READ_CHUNK_SIZE 65536 +#define MAX_DIGESTS_IN_PROGRESS 16 +#define SERVER_URI_TEMPLATE "http://dellbert.differnet.com/get_notes.cgi?ids=%s" +#define SERVER_POST_URI "http://dellbert.differnet.com/set_notes.cgi" +#define NOTES_LOOKUP_INTERVAL 3600 + +static int open_count = 0; +static int close_count = 0; +static int digests_in_progress = 0; + +static GList* digest_request_queue = NULL; +static GList* annotation_request_queue = NULL; + +static GHashTable *files_awaiting_annotation = NULL; + +static void md5_transform (guint32 buf[4], const guint32 in[16]); + +static int _ie = 0x44332211; +static union _endian { int i; char b[4]; } *_endian = (union _endian *)&_ie; +#define IS_BIG_ENDIAN() (_endian->b[0] == '\x44') +#define IS_LITTLE_ENDIAN() (_endian->b[0] == '\x11') + +static void got_file_digest (NautilusFile *file, const char *file_digest); +static void process_digest_requests (void); + +/* + * Note: this code is harmless on little-endian machines. + */ +static void +_byte_reverse (guchar *buf, guint32 longs) +{ + guint32 t; + do { + t = (guint32) ((guint32) buf[3] << 8 | buf[2]) << 16 | + ((guint32) buf[1] << 8 | buf[0]); + *(guint32 *) buf = t; + buf += 4; + } while (--longs); +} + +/** + * md5_init: Initialise an md5 context object + * @ctx: md5 context + * + * Initialise an md5 buffer. + * + **/ +static void +md5_init (MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; + + if (IS_BIG_ENDIAN()) + ctx->doByteReverse = 1; + else + ctx->doByteReverse = 0; +} + + + +/** + * md5_update: add a buffer to md5 hash computation + * @ctx: conetxt object used for md5 computaion + * @buf: buffer to add + * @len: buffer length + * + * Update context to reflect the concatenation of another buffer full + * of bytes. Use this to progressively construct an md5 hash. + **/ +static void +md5_update (MD5Context *ctx, const guchar *buf, guint32 len) +{ + guint32 t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((guint32) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + guchar *p = (guchar *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy (p, buf, len); + return; + } + memcpy (p, buf, t); + if (ctx->doByteReverse) + _byte_reverse (ctx->in, 16); + md5_transform (ctx->buf, (guint32 *) ctx->in); + buf += t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy (ctx->in, buf, 64); + if (ctx->doByteReverse) + _byte_reverse (ctx->in, 16); + md5_transform (ctx->buf, (guint32 *) ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy (ctx->in, buf, len); +} + + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +/** + * md5_final: copy the final md5 hash to a bufer + * @digest: 16 bytes buffer + * @ctx: context containing the calculated md5 + * + * copy the final md5 hash to a bufer + **/ +static void +md5_final (MD5Context *ctx, guchar digest[16]) +{ + guint32 count; + guchar *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset (p, 0, count); + if (ctx->doByteReverse) + _byte_reverse (ctx->in, 16); + md5_transform (ctx->buf, (guint32 *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset (ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset (p, 0, count - 8); + } + if (ctx->doByteReverse) + _byte_reverse (ctx->in, 14); + + /* Append length in bits and transform */ + ((guint32 *) ctx->in)[14] = ctx->bits[0]; + ((guint32 *) ctx->in)[15] = ctx->bits[1]; + + md5_transform (ctx->buf, (guint32 *) ctx->in); + if (ctx->doByteReverse) + _byte_reverse ((guchar *) ctx->buf, 4); + memcpy (digest, ctx->buf, 16); +} + + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. md5_Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void +md5_transform (guint32 buf[4], const guint32 in[16]) +{ + register guint32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP (F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP (F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP (F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP (F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP (F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP (F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP (F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP (F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP (F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP (F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP (F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP (F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP (F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP (F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP (F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP (F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP (F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP (F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP (F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP (F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP (F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP (F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP (F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP (F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP (F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP (F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP (F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP (F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP (F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP (F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP (F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP (F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP (F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP (F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP (F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP (F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP (F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP (F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP (F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP (F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP (F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP (F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP (F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP (F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP (F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP (F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP (F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP (F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP (F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP (F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP (F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP (F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP (F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP (F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP (F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP (F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP (F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP (F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP (F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP (F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP (F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP (F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP (F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP (F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + + + +/* When close is complete, there's no more work to do. */ +static void +digest_file_close_callback (GnomeVFSAsyncHandle *handle, + GnomeVFSResult result, + gpointer callback_data) +{ + close_count += 1; + g_message ("opened %d, closed %d", open_count, close_count); +} + +/* Close the file and then tell the caller we succeeded, handing off + * the buffer to the caller. + */ +static void +digest_file_completed (NautilusDigestFileHandle *digest_handle) +{ + guchar digest_result[16]; + char digest_string [33]; + char* hex_string = "0123456789abcdef"; + int index, result_index; + int current_value; + + if (digest_handle->opened) { + + gnome_vfs_async_close (digest_handle->handle, + digest_file_close_callback, + NULL); + } + + digests_in_progress -= 1; + + /* Invoke the callback to continue processing the annotation */ + md5_final (&digest_handle->digest_context, digest_result); + + /* make a hex string for the digest result */ + digest_string[32] = '\0'; + for (index = 0; index < 32; index++) { + current_value = digest_result[index >> 1]; + if (index & 1) { + result_index = current_value & 15; + } else { + result_index = (current_value >> 4) & 15; + } + + digest_string[index] = hex_string[result_index]; + } + + (* digest_handle->callback) (digest_handle->file, &digest_string[0]); + + nautilus_file_unref (digest_handle->file); + g_free (digest_handle->buffer); + g_free (digest_handle); + + /* start new digest requests if necessary */ + process_digest_requests (); + +} + +/* Tell the caller we failed. */ +static void +digest_file_failed (NautilusDigestFileHandle *digest_handle, GnomeVFSResult result) +{ + if (digest_handle->opened) { + gnome_vfs_async_close (digest_handle->handle, + digest_file_close_callback, + NULL); + } + g_free (digest_handle->buffer); + + digests_in_progress -= 1; + + (* digest_handle->callback) (digest_handle->file, NULL); + + nautilus_file_unref (digest_handle->file); + g_free (digest_handle); + + /* start new digest requests if necessary */ + process_digest_requests (); +} + +/* Here is the callback from the file read routine, where we actually accumulate the checksum */ +static void +calculate_checksum_callback (GnomeVFSAsyncHandle *handle, + GnomeVFSResult result, + gpointer buffer, + GnomeVFSFileSize bytes_requested, + GnomeVFSFileSize bytes_read, + gpointer callback_data) +{ + NautilusDigestFileHandle *digest_handle; + + /* Do a few reality checks. */ + g_assert (bytes_requested == READ_CHUNK_SIZE); + + digest_handle = callback_data; + g_assert (digest_handle->handle == handle); + g_assert (bytes_read <= bytes_requested); + + /* Check for a failure. */ + if (result != GNOME_VFS_OK && result != GNOME_VFS_ERROR_EOF) { + digest_file_failed (digest_handle, result); + return; + } + + /* accumulate the recently read data into the checksum */ + if (bytes_read > 0) { + md5_update (&digest_handle->digest_context, buffer, bytes_read); + } + + /* Read more unless we are at the end of the file. */ + if (bytes_read > 0 && result == GNOME_VFS_OK) { + gnome_vfs_async_read (digest_handle->handle, + digest_handle->buffer, + READ_CHUNK_SIZE, + calculate_checksum_callback, + digest_handle); + } else { + digest_file_completed (digest_handle); + } +} + +/* Once the open is finished, read a first chunk. */ +static void +read_file_open_callback (GnomeVFSAsyncHandle *handle, + GnomeVFSResult result, + gpointer callback_data) +{ + NautilusDigestFileHandle *digest_handle; + char *name; + + digest_handle = callback_data; + g_assert (digest_handle->handle == handle); + + /* Handle the failure case. */ + if (result != GNOME_VFS_OK) { + name = nautilus_file_get_name (digest_handle->file); + g_message ("open failed, filename %s, error was %d", name, result); + g_free (name); + digest_file_failed (digest_handle, result); + return; + } + + /* read in the first chunk of the file */ + digest_handle->opened = TRUE; + open_count += 1; + gnome_vfs_async_read (digest_handle->handle, + digest_handle->buffer, + READ_CHUNK_SIZE, + calculate_checksum_callback, + digest_handle); +} + +/* calculate the digest for the passed-in file asynchronously, invoking the passed in + * callback when the calculation has been completed. + */ +static NautilusDigestFileHandle* +calculate_file_digest (NautilusFile *file, NautilusCalculateDigestCallback callback) +{ + NautilusDigestFileHandle *handle; + char *uri; + + + /* allocate a digest-handle structure to keep our state */ + + handle = g_new0 (NautilusDigestFileHandle, 1); + uri = nautilus_file_get_uri (file); + + handle->callback = callback; + handle->opened = FALSE; + handle->file = file; + nautilus_file_ref (file); + + /* allocate the buffer */ + handle->buffer = g_malloc (READ_CHUNK_SIZE); + + /* initialize the MD5 stuff */ + md5_init (&handle->digest_context); + + /* open the file */ + gnome_vfs_async_open (&handle->handle, + uri, + GNOME_VFS_OPEN_READ, + read_file_open_callback, + handle); + g_free (uri); + return handle; +} + +/* process the digest request queue, launching as many requests as we can handle */ +static void +process_digest_requests (void) +{ + GList *current_entry; + NautilusFile *file; + + while (digests_in_progress < MAX_DIGESTS_IN_PROGRESS && digest_request_queue != NULL) + { + /* pull entry off queue */ + current_entry = digest_request_queue; + digest_request_queue = current_entry->next; + + file = NAUTILUS_FILE (current_entry->data); + + /* initiate request */ + calculate_file_digest (file, (NautilusCalculateDigestCallback) got_file_digest); + + /* dispose of queue entry */ + nautilus_file_unref (file); + + g_list_free_1 (current_entry); + digests_in_progress += 1; + } +} + +/* queue the digest request, and start processing it if we haven't exceeded the limit of requests + * in progress + */ +static void +queue_file_digest_request (NautilusFile *file) +{ + /* if annotation lookup is disabled, don't bother to do all this work */ + if (!nautilus_preferences_get_boolean (NAUTILUS_PREFERENCES_LOOKUP_ANNOTATIONS)) { + return; + } + nautilus_file_ref (file); + digest_request_queue = g_list_append (digest_request_queue, file); + process_digest_requests (); +} + +/* given a digest, retrieve an associated file object from the hash table */ +static NautilusFile * +get_file_from_digest (const char *digest) +{ + if (files_awaiting_annotation == NULL) { + return NULL; + } + + return g_hash_table_lookup (files_awaiting_annotation, digest); +} + +/* given a digest value, return the path to it in the local cache */ +static char * +get_annotation_path (const char *digest) +{ + char *user_directory, *annotation_directory; + char *annotation_path, *directory_uri; + + user_directory = nautilus_get_user_directory (); + annotation_directory = nautilus_make_path (user_directory, "annotations"); + annotation_path = nautilus_make_path (annotation_directory, digest); + + /* create the annotation directory if it doesn't exist */ + if (!g_file_exists (annotation_directory)) { + directory_uri = gnome_vfs_get_uri_from_local_path (annotation_directory); + gnome_vfs_make_directory (directory_uri, + GNOME_VFS_PERM_USER_ALL + | GNOME_VFS_PERM_GROUP_ALL + | GNOME_VFS_PERM_OTHER_READ); + g_free (directory_uri); + } + + /* free up the intermediate strings and return the complete path */ + g_free (user_directory); + g_free (annotation_directory); + + return annotation_path; +} + +/* look up the passed-in digest in the local annotation cache */ +static char * +look_up_local_annotation (NautilusFile *file, const char *digest) +{ + GnomeVFSResult result; + int file_size; + char *uri, *path, *file_data, *buffer; + + path = get_annotation_path (digest); + if (g_file_exists (path)) { + /* load the file and return it */ + uri = gnome_vfs_get_uri_from_local_path (path); + result = eel_read_entire_file (uri, &file_size, &file_data); + g_free (uri); + g_free (path); + if (result == GNOME_VFS_OK) { + /* add a null at the end, so it's a valid string */ + buffer = g_realloc (file_data, file_size + 1); + buffer[file_size] = '\0'; + return buffer; + } else { + return NULL; + } + } + g_free (path); + return NULL; +} + +static gboolean +has_local_annotation (const char *digest) +{ + gboolean has_annotation; + char *path; + + path = get_annotation_path (digest); + has_annotation = g_file_exists (path); + + g_free (path); + return has_annotation; +} + +/* utility routine to save the passed-in xml document as a local annotations file */ +static void +save_local_annotations (xmlDocPtr document, const char *digest) +{ + char *path; + + path = get_annotation_path (digest); + xmlSaveFile (path, document); + + g_free (path); +} + +/* utility routine to add the passed-in xml node to the file associated with the passed-in + * digest. If there isn't a file, create one + */ +static void +add_annotations_to_file (xmlNodePtr node_ptr, const char *digest) +{ + char *digest_path; + xmlDocPtr document; + + digest_path = get_annotation_path (digest); + + /* save the subtree as a new document, by making a new document and adding the new node */ + document = xmlNewDoc ("1.0"); + xmlDocSetRootElement (document, node_ptr); + + /* save the xml tree as a file in the cache area */ + xmlSaveFile (digest_path, document); + + xmlFreeDoc (document); + g_free (digest_path); +} + +/* remember the file object by adding it to a hash table */ +static void +remember_file (NautilusFile *file, const char *digest) +{ + nautilus_file_ref (file); + + if (files_awaiting_annotation == NULL) { + files_awaiting_annotation = g_hash_table_new (g_str_hash, g_str_equal); + /* g_atexit (annotations_file_table_free); */ + } + + g_hash_table_insert (files_awaiting_annotation, g_strdup (digest), file); +} + +/* forget a file when we're done with it by removing it from the table */ +static void +forget_file (const char *digest) +{ + NautilusFile *file; + if (files_awaiting_annotation == NULL) { + return; + } + + file = g_hash_table_lookup (files_awaiting_annotation, digest); + if (file != NULL) { + nautilus_file_unref (file); + g_hash_table_remove (files_awaiting_annotation, digest); + } +} + +/* completion routine invoked when we've loaded the an annotation file from the service. + * We must parse it, and walk through it to save the annotations in the local cache. + */ +static void +got_annotations_callback (GnomeVFSResult result, + GnomeVFSFileSize file_size, + char *file_contents, + gpointer callback_data) +{ + NautilusFile *file; + xmlDocPtr annotations; + xmlNodePtr next_annotation, item; + xmlNodePtr saved_annotation; + int annotation_count; + char *buffer, *digest, *info_str; + time_t date_stamp; + + /* exit if there was an error */ + if (result != GNOME_VFS_OK) { + g_assert (file_contents == NULL); + return; + } + + /* inexplicably, the gnome-xml parser requires a zero-terminated array, so add the null at the end. */ + buffer = g_realloc (file_contents, file_size + 1); + buffer[file_size] = '\0'; + annotations = xmlParseMemory (buffer, file_size); + g_free (buffer); + + /* iterate through the xml document, handling each annotation entry */ + if (annotations != NULL) { + next_annotation = xmlDocGetRootElement (annotations)->childs; + while (next_annotation != NULL) { + if (eel_strcmp (next_annotation->name, "annotations") == 0) { + /* get the digest associated with the annotations */ + digest = xmlGetProp (next_annotation, "digest"); + if (digest != NULL) { + /* count the number of annotations contained in the node */ + annotation_count = 0; + item = next_annotation->childs; + while (item != NULL) { + if (eel_strcmp (item->name, "annotation") == 0) { + annotation_count += 1; + } + item = item->next; + } + + /* write the annotation out to our cache area, if necessary */ + if (annotation_count > 0) { + saved_annotation = xmlCopyNode (next_annotation, TRUE); + add_annotations_to_file (saved_annotation, digest); + } + + /* retrieve the file object, and update it's count and time stamp */ + + file = get_file_from_digest (digest); + time (&date_stamp); + info_str = g_strdup_printf ("%lu:%d", date_stamp, annotation_count); + + nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_NOTES_INFO, NULL, info_str); + g_free (info_str); + + /* issue the changed signal */ + nautilus_file_emit_changed (file); + + /* remove the file from the hash table and unref it */ + forget_file (digest); + xmlFree (digest); + } + } + next_annotation = next_annotation->next; + } + + + /* free the xml document */ + xmlFreeDoc (annotations); + } +} + +/* format the request, and send it to the server */ +/* the first cut implementation simply sends the digests as a cgi parameter, + * but soon we'll want use SOAP or XML-RPC + */ +static void +fetch_annotations_from_server (void) +{ + GString *temp_string; + GList *current_entry, *save_entry; + char *uri; + + /* check to see if there are enough requests, or a long enough delay since the last one */ + + current_entry = annotation_request_queue; + save_entry = current_entry; + annotation_request_queue = NULL; + + /* simple cgi-based request format passed the digests as part of the uri, so + * gather the variable parts + */ + temp_string = g_string_new (""); + while (current_entry != NULL) { + g_string_append (temp_string, (char*) current_entry->data); + if (current_entry->next != NULL) { + g_string_append (temp_string, ","); + } + current_entry = current_entry->next; + } + + + uri = g_strdup_printf (SERVER_URI_TEMPLATE, temp_string->str); + g_string_free (temp_string, TRUE); + eel_g_list_free_deep (save_entry); + + /* read the result from the server asynchronously */ + eel_read_entire_file_async (uri, got_annotations_callback, NULL); + g_free (uri); +} + + +/* ask the server for an annotation asynchronously */ +static void +get_annotation_from_server (NautilusFile *file, const char *file_digest) +{ + /* see if there's a request for this one already pending - if so, we can return */ + if (get_file_from_digest (file_digest) != NULL) { + return; + } + + /* only do this if lookups are enabled */ + /* if annotation lookup is disabled, don't bother to do all this work */ + if (!nautilus_preferences_get_boolean (NAUTILUS_PREFERENCES_LOOKUP_ANNOTATIONS)) { + return; + } + + /* add the request to the queue, and kick it off it there's enough of them */ + annotation_request_queue = g_list_prepend (annotation_request_queue, g_strdup (file_digest)); + + remember_file (file, file_digest); + fetch_annotations_from_server (); +} + +/* callback that's invokes when we've finished calculating the file's digest. Remember + * it in the metadata, and look up the associated annotation + */ +static void +got_file_digest (NautilusFile *file, const char *file_digest) +{ + + if (file_digest == NULL) { + return; + } + + /* save the digest in the file metadata */ + nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_FILE_DIGEST, NULL, file_digest); + + /* lookup the annotations associated with the file. If there is one, flag the change and we're done */ + if (has_local_annotation (file_digest)) { + nautilus_file_emit_changed (file); + return; + } + + /* there isn't a local annotation, so ask the server for one */ + get_annotation_from_server (file, file_digest); + return; +} + +/* utility routine that takes a passed-in notes-info string and returns true if the + * encoded date is old enough to require a new look-up + */ +static gboolean +annotation_is_stale (const char *notes_info) +{ + time_t info_date, date_stamp; + + if (notes_info == NULL) { + return TRUE; + } + + info_date = strtoul (notes_info, NULL, 10); + time (&date_stamp); + + /* eventually, the lookup interval should be a preference, not a constant */ + return (date_stamp - info_date) > NOTES_LOOKUP_INTERVAL; +} + +/* return the annotation associated with a file. If we haven't inspected this file yet, + * return NULL but queue a request for an annotation lookup, which will be processed + * asynchronously and issue a "file_changed" signal if any is found. + */ +char *nautilus_annotation_get_annotation (NautilusFile *file) +{ + char *digest; + char *annotations; + char *digest_info; + + /* if it's a directory, return NULL, at least until we figure out how to handle directory + * annotations + */ + if (nautilus_file_is_directory (file)) { + return NULL; + } + + /* see if there's a digest available in metadata */ + digest = nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_FILE_DIGEST, NULL); + + /* there isn't a digest, so start a request for one going, and return NULL */ + if (digest == NULL) { + queue_file_digest_request (file); + return NULL; + } + + /* if we haven't update the info in a while, initiate a lookup */ + digest_info = nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_NOTES_INFO, NULL); + if (annotation_is_stale (digest_info)) { + get_annotation_from_server (file, digest); + } + g_free (digest_info); + + /* there's a digest, so we if we have the annotations for the file cached locally */ + annotations = look_up_local_annotation (file, digest); + if (annotations != NULL) { + g_free (digest); + return annotations; + } + + g_free (digest); + return NULL; +} + +/* utility routine to map raw annotation text into text to be displayed + * for now this is pretty naive and only handles free-form text, just returning + * the first suitable annotation it can find. + */ +char * +nautilus_annotation_get_display_text (const char *note_text) +{ + char *display_text, *temp_text; + xmlChar *xml_text; + xmlDocPtr annotations; + xmlNodePtr next_annotation; + + /* if its an xml file, parse it to extract the display text */ + if (eel_istr_has_prefix (note_text, "<?xml")) { + display_text = NULL; + annotations = xmlParseMemory ((char*) note_text, strlen (note_text)); + if (annotations != NULL) { + next_annotation = xmlDocGetRootElement (annotations)->childs; + while (next_annotation != NULL) { + if (eel_strcmp (next_annotation->name, "annotation") == 0) { + xml_text = xmlNodeGetContent (next_annotation); + temp_text = (char*) xml_text; + while (*temp_text && *temp_text < ' ') temp_text++; + display_text = g_strdup (temp_text); + xmlFree (xml_text); + break; + } + next_annotation = next_annotation->next; + } + xmlFreeDoc (annotations); + } + } else { + display_text = g_strdup (note_text); + } + return display_text; +} + +/* convenience routine to return the display text of an annotation associated + * with a file + */ +char * +nautilus_annotation_get_annotation_for_display (NautilusFile *file) +{ + char *raw_text, *display_text; + + raw_text = nautilus_annotation_get_annotation (file); + if (raw_text != NULL) { + display_text = nautilus_annotation_get_display_text (raw_text); + g_free (raw_text); + return display_text; + } + return NULL; +} + +/* return the number of annotations associated with the passed in file. If we don't know, + * return 0, but queue a request like above + */ +int nautilus_annotation_has_annotation (NautilusFile *file) +{ + char *digest_info, *digits, *temp_str; + int count = 0; + + if (!nautilus_preferences_get_boolean (NAUTILUS_PREFERENCES_DISPLAY_ANNOTATIONS)) { + return 0; + } + + digest_info = nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_NOTES_INFO, NULL); + + if (digest_info != NULL) { + digits = strrchr (digest_info, ':'); + count = atoi (digits + 1); + g_free (digest_info); + + /* if the date is stale, launch a new lookup */ + if (annotation_is_stale (digest_info)) { + temp_str = nautilus_annotation_get_annotation (file); + g_free (temp_str); + } + return count; + } else { + /* initiate fetching the annotations from the server */ + temp_str = nautilus_annotation_get_annotation (file); + g_free (temp_str); + } + g_free (digest_info); + return 0; +} + +/* utility routine for making an HTTP POST request with ghttp */ +static gboolean +http_post_simple (const char *uri, const char *name, const char *value) { + ghttp_request* request; + char *ename=NULL, *evalue=NULL, *body=NULL; + + request = ghttp_request_new (); + if (!request) { + return FALSE; + } + + if (ghttp_set_uri (request, (char *)uri) != 0 || + ghttp_set_type (request, ghttp_type_post) != 0) { + ghttp_close (request); + return FALSE; + } + ghttp_set_header (request, http_hdr_Connection, "close"); + ghttp_set_header (request, http_hdr_User_Agent, "Nautilus Annotation"); + ghttp_set_header (request, http_hdr_Content_Type, "application/x-www-form-urlencoded"); + + evalue = gnome_vfs_escape_string (value); + if (name) { + ename = gnome_vfs_escape_string (name); + body = g_strconcat (ename, "=", evalue, NULL); + g_free (ename); + g_free (evalue); + } else { + body = evalue; + } + + if (ghttp_set_body (request, body, strlen(body)) != 0 || + ghttp_prepare (request) != 0) { + ghttp_close (request); + return FALSE; + } + + if (ghttp_process (request) != ghttp_done) { + ghttp_close (request); + return FALSE; + } + + ghttp_close (request); + return TRUE; +} + +/* utility to count the number of annotations in the passed-in xml document */ +static int +count_annotations (xmlDocPtr xml_document) +{ + xmlNodePtr next_annotation; + int annotation_count; + + annotation_count = 0; + next_annotation = xmlDocGetRootElement (xml_document)->childs; + + while (next_annotation != NULL) { + if (eel_strcmp (next_annotation->name, "annotation") == 0) { + annotation_count += 1; + } + next_annotation = next_annotation->next; + } + return annotation_count; +} + +/* + * get_ammonite_get_default_user_username + * + * Returns username of the currently logged-in default Eazel Service User + * or NULL if there isn't one + */ + + +/* send the local annotations associated with the passed-in digest to the server */ +static void +nautilus_annotation_send_to_server (const char *digest, + const char *annotation_type, + const char *annotation_text, + const char *date_str) +{ + char *user_id; + xmlChar *request_text; + xmlDocPtr xml_document; + xmlNodePtr root_node, annotation_node; + int request_size; + + /* get the user name */ + user_id = ammonite_get_default_user_username (); + + /* if the user wasn't logged in, prompt for it (coming soon, for now, just use anonymous */ + if (user_id == NULL) { + user_id = g_strdup ("anonymous"); + } + + /* create an xml document to hold the annotation posting */ + xml_document = xmlNewDoc ("1.0"); + + /* create the header node, with the digest attribute */ + root_node = xmlNewDocNode (xml_document, NULL, "annotations", NULL); + xmlDocSetRootElement (xml_document, root_node); + xmlSetProp (root_node, "digest", digest); + xmlSetProp (root_node, "user", user_id); + + /* set up the annotation payload */ + annotation_node = xmlNewChild (root_node, NULL, "annotation", NULL); + xmlSetProp (annotation_node, "type", annotation_type); + xmlSetProp (annotation_node, "date", date_str); + + xmlNodeSetContent (annotation_node, annotation_text); + + /* post the annotation request to the server using ghttp */ + xmlDocDumpMemory (xml_document, &request_text, &request_size); + + if (!http_post_simple (SERVER_POST_URI, "note", request_text)) { + g_message ("post request failed"); + } + + /* clean up and we're done */ + xmlFree (request_text); + g_free (user_id); + xmlFreeDoc (xml_document); + +} + +/* add an annotation to a file */ +void +nautilus_annotation_add_annotation (NautilusFile *file, + const char *annotation_type, + const char *annotation_text, + const char *access) +{ + char *digest; + char *annotations; + char *info_str, *date_str; + char *annotation_path; + int annotation_count; + time_t date_stamp; + xmlDocPtr xml_document; + xmlNodePtr root_node, node; + gboolean has_annotation, has_new_annotation; + + /* we can't handle directories yet, so just return. */ + if (nautilus_file_is_directory (file)) { + return; + } + + has_new_annotation = annotation_text != NULL && strlen (annotation_text) > 0; + + /* fetch the local annotation, if one exists */ + digest = nautilus_file_get_metadata (file, NAUTILUS_METADATA_KEY_FILE_DIGEST, NULL); + + /* there isn't a digest, so start a request for one going, and return */ + /* this shouldn't happen in practice, since the annotation window will have + * already created a digest + */ + if (digest == NULL) { + queue_file_digest_request (file); + return; + } + + /* there's a digest, so we if we have the annotations for the file cached locally */ + annotations = look_up_local_annotation (file, digest); + has_annotation = annotations != NULL && strlen (annotations) > 0; + + /* handle the case of no annotation, by removing it if necessary */ + if (!has_new_annotation) { + if (has_annotation) { + /* delete the annotation */ + annotation_path = get_annotation_path (digest); + unlink (annotation_path); + + /* set the info to indicate no notes available */ + time (&date_stamp); + info_str = g_strdup_printf ("%lu:%d", date_stamp, 0); + nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_NOTES_INFO, NULL, info_str); + g_free (info_str); + + nautilus_file_emit_changed (file); + + g_free (annotation_path); + } + + g_free (digest); + g_free (annotations); + return; + } + + /* there is a new annotation, but no current annotation exists, so create the + * initial xml document from scratch + */ + if (!has_annotation) { + xml_document = xmlNewDoc ("1.0"); + /* create the header node, with the digest attribute */ + root_node = xmlNewDocNode (xml_document, NULL, "annotations", NULL); + xmlDocSetRootElement (xml_document, root_node); + xmlSetProp (root_node, "digest", digest); + } else { + /* open the existing annotation and load it */ + xml_document = xmlParseMemory (annotations, strlen (annotations)); + root_node = xmlDocGetRootElement (xml_document); + } + + time (&date_stamp); + date_str = g_strdup_printf ("%lu", date_stamp); + + /* add the new entry. For now, we only support one entry per file, so we replace the old + * one, if it exists, but this will change soon as we support multiple notes per file + */ + if (root_node->childs == NULL) { + node = xmlNewChild (root_node, NULL, "annotation", NULL); + xmlSetProp (node, "type", annotation_type); + + date_str = g_strdup_printf ("%lu", date_stamp); + xmlSetProp (node, "date", date_str); + } else { + node = root_node->childs; + } + + xmlNodeSetContent (node, annotation_text); + + /* save the modified xml document back to the local repository */ + save_local_annotations (xml_document, digest); + + /* update the metadata date and count */ + annotation_count = count_annotations (xml_document); + info_str = g_strdup_printf ("%lu:%d", date_stamp, annotation_count); + + nautilus_file_set_metadata (file, NAUTILUS_METADATA_KEY_NOTES_INFO, NULL, info_str); + g_free (info_str); + + /* issue file changed symbol to update the emblem */ + nautilus_file_emit_changed (file); + + /* if the access is global, send it to the server */ + if (eel_strcmp (access, "global") == 0) { + nautilus_annotation_send_to_server (digest, annotation_type, annotation_text, date_str); + } + + /* clean up and we're done */ + xmlFreeDoc (xml_document); + g_free (date_str); + g_free (digest); + g_free (annotations); +} + +/* remove an annotation from a file */ +void nautilus_annotation_remove_annotation (NautilusFile *file, int which_annotation) +{ +} + diff --git a/libnautilus-private/nautilus-annotation.h b/libnautilus-private/nautilus-annotation.h new file mode 100644 index 000000000..712b276f8 --- /dev/null +++ b/libnautilus-private/nautilus-annotation.h @@ -0,0 +1,45 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- + + nautilus-annotation.h: routines for getting and setting xml-based annotations associated + with the digest of a file. + + Copyright (C) 2000 Eazel, Inc. + + 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: Andy Hertzfeld <andy@eazel.com> +*/ + +#ifndef NAUTILUS_ANNOTATION_H +#define NAUTILUS_ANNOTATION_H + +#include <glib.h> +#include <libnautilus-extensions/nautilus-file.h> +#include <libnautilus-extensions/nautilus-metadata.h> + +char * nautilus_annotation_get_annotation (NautilusFile *file); +char * nautilus_annotation_get_display_text (const char* annotation_text); +char * nautilus_annotation_get_annotation_for_display (NautilusFile *file); + +int nautilus_annotation_has_annotation (NautilusFile *file); + +void nautilus_annotation_add_annotation (NautilusFile *file, + const char *annotation_type, + const char *annotation_text, + const char *access); +void nautilus_annotation_remove_annotation (NautilusFile *file, int which_annotation); + +#endif /* NAUTILUS_ANNOTATION_H */ diff --git a/libnautilus-private/nautilus-canvas-note-item.c b/libnautilus-private/nautilus-canvas-note-item.c new file mode 100644 index 000000000..6f3607090 --- /dev/null +++ b/libnautilus-private/nautilus-canvas-note-item.c @@ -0,0 +1,973 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- + + nautilus-canvas-note-item.c: annotation canvas item for nautilus implementation + + Copyright (C) 2001 Eazel, Inc. + + 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. + + based on gnome_canvas_rect_item by Federico Mena Quintero + + Author: Andy Hertzfeld <andy@eazel.com> +*/ + +#include <config.h> +#include <math.h> + +#include <libgnomeui/gnome-canvas.h> +#include <libgnomeui/gnome-canvas-util.h> +#include <libgnomeui/gnome-icon-text.h> + +#include <gnome-xml/parser.h> +#include <gnome-xml/xmlmemory.h> + +#include <libart_lgpl/art_vpath.h> +#include <libart_lgpl/art_svp.h> +#include <libart_lgpl/art_svp_vpath.h> +#include <libart_lgpl/art_rgb_svp.h> + +#include "nautilus-annotation.h" +#include "nautilus-canvas-note-item.h" +#include "nautilus-font-factory.h" +#include <eel/eel-gdk-extensions.h> +#include <eel/eel-gdk-pixbuf-extensions.h> +#include <eel/eel-gnome-extensions.h> +#include <eel/eel-scalable-font.h> +#include <eel/eel-smooth-text-layout.h> +#include <eel/eel-string.h> + +enum { + ARG_0, + ARG_X1, + ARG_Y1, + ARG_X2, + ARG_Y2, + ARG_FILL_COLOR, + ARG_FILL_COLOR_GDK, + ARG_FILL_COLOR_RGBA, + ARG_NOTE_TEXT, + ARG_OUTLINE_COLOR, + ARG_OUTLINE_COLOR_GDK, + ARG_OUTLINE_COLOR_RGBA, + ARG_FILL_STIPPLE, + ARG_OUTLINE_STIPPLE, + ARG_WIDTH_PIXELS, + ARG_WIDTH_UNITS +}; + +#define ANNOTATION_MAX_WIDTH 240 +#define DEFAULT_FONT_SIZE 12 +#define LINE_BREAK_CHARACTERS " -_,;.?/&" + +#define ARROW_HEIGHT 16 +#define MIN_ARROW_HALF_WIDTH 4 +#define MAX_ARROW_HALF_WIDTH 12 + +static void nautilus_canvas_note_item_class_init (NautilusCanvasNoteItemClass *class); +static void nautilus_canvas_note_item_init (NautilusCanvasNoteItem *note_item); +static void nautilus_canvas_note_item_destroy (GtkObject *object); +static void nautilus_canvas_note_item_set_arg (GtkObject *object, + GtkArg *arg, + guint arg_id); +static void nautilus_canvas_note_item_get_arg (GtkObject *object, + GtkArg *arg, + guint arg_id); + +static void nautilus_canvas_note_item_realize (GnomeCanvasItem *item); +static void nautilus_canvas_note_item_unrealize (GnomeCanvasItem *item); +static void nautilus_canvas_note_item_translate (GnomeCanvasItem *item, double dx, double dy); +static void nautilus_canvas_note_item_bounds (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2); + +static void nautilus_canvas_note_item_draw (GnomeCanvasItem *item, GdkDrawable *drawable, int x, int y, int width, int height); +static void nautilus_canvas_note_item_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf); +static void nautilus_canvas_note_item_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags); +static double nautilus_canvas_note_item_point (GnomeCanvasItem *item, double x, double y, int cx, int cy, GnomeCanvasItem **actual_item); + +static GnomeCanvasItemClass *note_item_parent_class; + + +GtkType +nautilus_canvas_note_item_get_type (void) +{ + static GtkType note_item_type = 0; + + if (!note_item_type) { + GtkTypeInfo note_item_info = { + "NautilusCanvasNoteItem", + sizeof (NautilusCanvasNoteItem), + sizeof (NautilusCanvasNoteItemClass), + (GtkClassInitFunc) nautilus_canvas_note_item_class_init, + (GtkObjectInitFunc) nautilus_canvas_note_item_init, + NULL, /* reserved_1 */ + NULL, /* reserved_2 */ + (GtkClassInitFunc) NULL + }; + + note_item_type = gtk_type_unique (gnome_canvas_item_get_type (), ¬e_item_info); + } + + return note_item_type; +} + +static void +nautilus_canvas_note_item_class_init (NautilusCanvasNoteItemClass *class) +{ + GtkObjectClass *object_class; + GnomeCanvasItemClass *item_class; + + object_class = (GtkObjectClass *) class; + item_class = (GnomeCanvasItemClass *) class; + + note_item_parent_class = gtk_type_class (gnome_canvas_item_get_type ()); + + gtk_object_add_arg_type ("NautilusCanvasNoteItem::x1", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_X1); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::y1", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_Y1); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::x2", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_X2); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::y2", GTK_TYPE_DOUBLE, GTK_ARG_READWRITE, ARG_Y2); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::fill_color", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_FILL_COLOR); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::fill_color_gdk", GTK_TYPE_GDK_COLOR, GTK_ARG_READWRITE, ARG_FILL_COLOR_GDK); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::fill_color_rgba", GTK_TYPE_UINT, GTK_ARG_READWRITE, ARG_FILL_COLOR_RGBA); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::note_text", GTK_TYPE_STRING, GTK_ARG_READWRITE, ARG_NOTE_TEXT); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::outline_color", GTK_TYPE_STRING, GTK_ARG_WRITABLE, ARG_OUTLINE_COLOR); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::outline_color_gdk", GTK_TYPE_GDK_COLOR, GTK_ARG_READWRITE, ARG_OUTLINE_COLOR_GDK); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::outline_color_rgba", GTK_TYPE_UINT, GTK_ARG_READWRITE, ARG_OUTLINE_COLOR_RGBA); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::fill_stipple", GTK_TYPE_GDK_WINDOW, GTK_ARG_READWRITE, ARG_FILL_STIPPLE); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::outline_stipple", GTK_TYPE_GDK_WINDOW, GTK_ARG_READWRITE, ARG_OUTLINE_STIPPLE); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::width_pixels", GTK_TYPE_UINT, GTK_ARG_WRITABLE, ARG_WIDTH_PIXELS); + gtk_object_add_arg_type ("NautilusCanvasNoteItem::width_units", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_WIDTH_UNITS); + + object_class->destroy = nautilus_canvas_note_item_destroy; + object_class->set_arg = nautilus_canvas_note_item_set_arg; + object_class->get_arg = nautilus_canvas_note_item_get_arg; + + item_class->realize = nautilus_canvas_note_item_realize; + item_class->unrealize = nautilus_canvas_note_item_unrealize; + item_class->translate = nautilus_canvas_note_item_translate; + item_class->bounds = nautilus_canvas_note_item_bounds; + + item_class->draw = nautilus_canvas_note_item_draw; + item_class->point = nautilus_canvas_note_item_point; + item_class->update = nautilus_canvas_note_item_update; + item_class->render = nautilus_canvas_note_item_render; +} + +static void +nautilus_canvas_note_item_init (NautilusCanvasNoteItem *note_item) +{ + note_item->width = 0.0; + note_item->fill_svp = NULL; + note_item->outline_svp = NULL; +} + +static void +nautilus_canvas_note_item_destroy (GtkObject *object) +{ + NautilusCanvasNoteItem *note_item; + + g_return_if_fail (object != NULL); + g_return_if_fail (NAUTILUS_IS_CANVAS_NOTE_ITEM (object)); + + note_item = NAUTILUS_CANVAS_NOTE_ITEM (object); + + if (note_item->fill_stipple) + gdk_bitmap_unref (note_item->fill_stipple); + + if (note_item->outline_stipple) + gdk_bitmap_unref (note_item->outline_stipple); + + if (note_item->fill_svp) + art_svp_free (note_item->fill_svp); + + if (note_item->outline_svp) + art_svp_free (note_item->outline_svp); + + if (note_item->note_text) + g_free (note_item->note_text); + + if (GTK_OBJECT_CLASS (note_item_parent_class)->destroy) + (* GTK_OBJECT_CLASS (note_item_parent_class)->destroy) (object); +} + +static void get_bounds (NautilusCanvasNoteItem *note_item, double *px1, double *py1, double *px2, double *py2) +{ + GnomeCanvasItem *item; + double x1, y1, x2, y2; + int cx1, cy1, cx2, cy2; + double hwidth; + + item = GNOME_CANVAS_ITEM (note_item); + + if (note_item->width_pixels) + hwidth = (note_item->width / item->canvas->pixels_per_unit) / 2.0; + else + hwidth = note_item->width / 2.0; + + x1 = note_item->x1; + y1 = note_item->y1; + x2 = note_item->x2; + y2 = note_item->y2; + + gnome_canvas_item_i2w (item, &x1, &y1); + gnome_canvas_item_i2w (item, &x2, &y2); + gnome_canvas_w2c (item->canvas, x1 - hwidth, y1 - hwidth, &cx1, &cy1); + gnome_canvas_w2c (item->canvas, x2 + hwidth, y2 + hwidth, &cx2, &cy2); + *px1 = cx1; + *py1 = cy1; + *px2 = cx2; + *py2 = cy2; + + /* Some safety fudging */ + + *px1 -= 2; + *py1 -= 2; + *px2 += 2; + *py2 += 2; +} + +/* Convenience function to set a GC's foreground color to the specified pixel value */ +static void +set_gc_foreground (GdkGC *gc, gulong pixel) +{ + GdkColor c; + + if (!gc) + return; + + c.pixel = pixel; + gdk_gc_set_foreground (gc, &c); +} + +/* Sets the stipple pattern for the specified gc */ +static void +set_stipple (GdkGC *gc, GdkBitmap **internal_stipple, GdkBitmap *stipple, int reconfigure) +{ + if (*internal_stipple && !reconfigure) + gdk_bitmap_unref (*internal_stipple); + + *internal_stipple = stipple; + if (stipple && !reconfigure) + gdk_bitmap_ref (stipple); + + if (gc) { + if (stipple) { + gdk_gc_set_stipple (gc, stipple); + gdk_gc_set_fill (gc, GDK_STIPPLED); + } else + gdk_gc_set_fill (gc, GDK_SOLID); + } +} + +/* Recalculate the outline width of the rectangle/ellipse and set it in its GC */ +static void +set_outline_gc_width (NautilusCanvasNoteItem *note_item) +{ + int width; + + if (!note_item->outline_gc) + return; + + if (note_item->width_pixels) + width = (int) note_item->width; + else + width = (int) (note_item->width * note_item->item.canvas->pixels_per_unit + 0.5); + + gdk_gc_set_line_attributes (note_item->outline_gc, width, + GDK_LINE_SOLID, GDK_CAP_PROJECTING, GDK_JOIN_MITER); +} + +/* utility to update the canvas item bounding box from the note item's private bounding box */ +static void +update_item_bounding_box (NautilusCanvasNoteItem *note_item) +{ + GnomeCanvasItem *item; + item = GNOME_CANVAS_ITEM (note_item); + + item->x1 = note_item->x1; + item->y1 = note_item->y1; + item->x2 = note_item->x2 + 1; + item->y2 = note_item->y2 + 1; +} + +static void +nautilus_canvas_note_item_set_note_text (NautilusCanvasNoteItem *note_item, const char *new_text) +{ + char *display_text; + int text_width, height, width; + int font_height; + GnomeCanvasItem *item; + EelScalableFont *scalable_font; + GdkFont *font; + EelDimensions dimensions; + EelSmoothTextLayout *smooth_text_layout; + + item = GNOME_CANVAS_ITEM (note_item); + + if (note_item->note_text) { + g_free (note_item->note_text); + } + + height = 0; width = 0; /* to avoid compiler complaint */ + note_item->note_text = g_strdup (new_text); + + /* set the width and height based on the display text */ + /* this will get more sophisticated as we get fancier */ + display_text = nautilus_annotation_get_display_text (new_text); + + if (item->canvas->aa) { + scalable_font = eel_scalable_font_get_default_font (); + + + smooth_text_layout = eel_smooth_text_layout_new ( + display_text, strlen(display_text), + scalable_font, DEFAULT_FONT_SIZE, TRUE); + + dimensions = eel_smooth_text_layout_get_dimensions (smooth_text_layout); + text_width = dimensions.width + 8; + height = dimensions.height + 4; + height += ARROW_HEIGHT; + gtk_object_unref (GTK_OBJECT (scalable_font)); + gtk_object_destroy (GTK_OBJECT (smooth_text_layout)); + + } else { + font = nautilus_font_factory_get_font_from_preferences (DEFAULT_FONT_SIZE); + text_width = 8 + gdk_text_measure (font, display_text, strlen (display_text)); + font_height = gdk_text_height (font, display_text, strlen (display_text)); + height = font_height * (1 + (text_width / ANNOTATION_MAX_WIDTH)); + gdk_font_unref (font); + } + + width = (text_width < ANNOTATION_MAX_WIDTH) ? text_width : ANNOTATION_MAX_WIDTH; + + /* add some vertical slop for descenders and incorporate scale factor */ + note_item->x2 = floor (note_item->x1 + (width / item->canvas->pixels_per_unit) + .5); + note_item->y2 = floor (note_item->y1 + 4.0 + (height / item->canvas->pixels_per_unit) + .5); + + + update_item_bounding_box (note_item); + + g_free (display_text); +} + +static void +nautilus_canvas_note_item_set_arg (GtkObject *object, GtkArg *arg, guint arg_id) +{ + GnomeCanvasItem *item; + NautilusCanvasNoteItem *note_item; + GdkColor color = { 0, 0, 0, 0, }; + GdkColor *pcolor; + int have_pixel; + + item = GNOME_CANVAS_ITEM (object); + note_item = NAUTILUS_CANVAS_NOTE_ITEM (object); + have_pixel = FALSE; + + switch (arg_id) { + case ARG_X1: + note_item->x1 = GTK_VALUE_DOUBLE (*arg); + update_item_bounding_box (note_item); + gnome_canvas_item_request_update (item); + break; + + case ARG_Y1: + note_item->y1 = GTK_VALUE_DOUBLE (*arg); + update_item_bounding_box (note_item); + gnome_canvas_item_request_update (item); + break; + + case ARG_X2: + note_item->x2 = GTK_VALUE_DOUBLE (*arg); + update_item_bounding_box (note_item); + gnome_canvas_item_request_update (item); + break; + + case ARG_Y2: + note_item->y2 = GTK_VALUE_DOUBLE (*arg); + update_item_bounding_box (note_item); + gnome_canvas_item_request_update (item); + break; + + case ARG_FILL_COLOR: + case ARG_FILL_COLOR_GDK: + case ARG_FILL_COLOR_RGBA: + switch (arg_id) { + case ARG_FILL_COLOR: + gdk_color_parse (GTK_VALUE_STRING (*arg), &color); + + note_item->fill_color = ((color.red & 0xff00) << 16 | + (color.green & 0xff00) << 8 | + (color.blue & 0xff00) | + 0xff); + break; + + case ARG_FILL_COLOR_GDK: + pcolor = GTK_VALUE_BOXED (*arg); + if (pcolor) { + color = *pcolor; + gdk_color_context_query_color (item->canvas->cc, &color); + have_pixel = TRUE; + } + + note_item->fill_color = ((color.red & 0xff00) << 16 | + (color.green & 0xff00) << 8 | + (color.blue & 0xff00) | + 0xff); + break; + + case ARG_FILL_COLOR_RGBA: + note_item->fill_color = GTK_VALUE_UINT (*arg); + break; + } + + if (have_pixel) + note_item->fill_pixel = color.pixel; + else + note_item->fill_pixel = gnome_canvas_get_color_pixel (item->canvas, note_item->fill_color); + + if (!item->canvas->aa) + set_gc_foreground (note_item->fill_gc, note_item->fill_pixel); + + gnome_canvas_item_request_redraw_svp (item, note_item->fill_svp); + break; + + case ARG_OUTLINE_COLOR: + case ARG_OUTLINE_COLOR_GDK: + case ARG_OUTLINE_COLOR_RGBA: + switch (arg_id) { + case ARG_OUTLINE_COLOR: + gdk_color_parse (GTK_VALUE_STRING (*arg), &color); + + note_item->outline_color = ((color.red & 0xff00) << 16 | + (color.green & 0xff00) << 8 | + (color.blue & 0xff00) | + 0xff); + break; + + case ARG_OUTLINE_COLOR_GDK: + pcolor = GTK_VALUE_BOXED (*arg); + if (pcolor) { + color = *pcolor; + gdk_color_context_query_color (item->canvas->cc, &color); + have_pixel = TRUE; + } + + note_item->outline_color = ((color.red & 0xff00) << 16 | + (color.green & 0xff00) << 8 | + (color.blue & 0xff00) | + 0xff); + break; + + case ARG_OUTLINE_COLOR_RGBA: + note_item->outline_color = GTK_VALUE_UINT (*arg); + break; + } + + if (have_pixel) + note_item->outline_pixel = color.pixel; + else + note_item->outline_pixel = gnome_canvas_get_color_pixel (item->canvas, + note_item->outline_color); + + if (!item->canvas->aa) + set_gc_foreground (note_item->outline_gc, note_item->outline_pixel); + + gnome_canvas_item_request_redraw_svp (item, note_item->outline_svp); + break; + + case ARG_NOTE_TEXT: + nautilus_canvas_note_item_set_note_text (note_item, GTK_VALUE_STRING (*arg)); + break; + + case ARG_FILL_STIPPLE: + if (!item->canvas->aa) + set_stipple (note_item->fill_gc, ¬e_item->fill_stipple, GTK_VALUE_BOXED (*arg), FALSE); + + break; + + case ARG_OUTLINE_STIPPLE: + if (!item->canvas->aa) + set_stipple (note_item->outline_gc, ¬e_item->outline_stipple, GTK_VALUE_BOXED (*arg), FALSE); + break; + + case ARG_WIDTH_PIXELS: + note_item->width = GTK_VALUE_UINT (*arg); + note_item->width_pixels = TRUE; + if (!item->canvas->aa) + set_outline_gc_width (note_item); + + gnome_canvas_item_request_update (item); + break; + + case ARG_WIDTH_UNITS: + note_item->width = fabs (GTK_VALUE_DOUBLE (*arg)); + note_item->width_pixels = FALSE; + if (!item->canvas->aa) + set_outline_gc_width (note_item); + + gnome_canvas_item_request_update (item); + break; + + default: + break; + } +} + +/* Allocates a GdkColor structure filled with the specified pixel, and puts it into the specified + * arg for returning it in the get_arg method. + */ +static void +get_color_arg (NautilusCanvasNoteItem *note_item, gulong pixel, GtkArg *arg) +{ + GdkColor *color; + + color = g_new (GdkColor, 1); + color->pixel = pixel; + gdk_color_context_query_color (GNOME_CANVAS_ITEM (note_item)->canvas->cc, color); + GTK_VALUE_BOXED (*arg) = color; +} + +static void +nautilus_canvas_note_item_get_arg (GtkObject *object, GtkArg *arg, guint arg_id) +{ + NautilusCanvasNoteItem *note_item; + GnomeCanvasItem *item; + + item = GNOME_CANVAS_ITEM (object); + note_item = NAUTILUS_CANVAS_NOTE_ITEM (object); + + switch (arg_id) { + case ARG_X1: + GTK_VALUE_DOUBLE (*arg) = note_item->x1; + break; + + case ARG_Y1: + GTK_VALUE_DOUBLE (*arg) = note_item->y1; + break; + + case ARG_X2: + GTK_VALUE_DOUBLE (*arg) = note_item->x2; + break; + + case ARG_Y2: + GTK_VALUE_DOUBLE (*arg) = note_item->y2; + break; + + case ARG_FILL_COLOR_GDK: + get_color_arg (note_item, note_item->fill_pixel, arg); + break; + + case ARG_OUTLINE_COLOR_GDK: + get_color_arg (note_item, note_item->outline_pixel, arg); + break; + + case ARG_FILL_COLOR_RGBA: + GTK_VALUE_UINT (*arg) = note_item->fill_color; + break; + + case ARG_OUTLINE_COLOR_RGBA: + GTK_VALUE_UINT (*arg) = note_item->outline_color; + break; + + case ARG_FILL_STIPPLE: + GTK_VALUE_BOXED (*arg) = note_item->fill_stipple; + break; + + case ARG_OUTLINE_STIPPLE: + GTK_VALUE_BOXED (*arg) = note_item->outline_stipple; + break; + + case ARG_NOTE_TEXT: + GTK_VALUE_STRING (*arg) = note_item->note_text; + break; + default: + arg->type = GTK_TYPE_INVALID; + break; + } +} + +static void +nautilus_canvas_note_item_realize (GnomeCanvasItem *item) +{ + NautilusCanvasNoteItem *note_item; + + note_item = NAUTILUS_CANVAS_NOTE_ITEM (item); + + if (note_item_parent_class->realize) + (* note_item_parent_class->realize) (item); + + if (!item->canvas->aa) { + note_item->fill_gc = gdk_gc_new (item->canvas->layout.bin_window); + note_item->outline_gc = gdk_gc_new (item->canvas->layout.bin_window); + } +} + +static void +nautilus_canvas_note_item_unrealize (GnomeCanvasItem *item) +{ + NautilusCanvasNoteItem *note_item; + + note_item = NAUTILUS_CANVAS_NOTE_ITEM (item); + + if (!item->canvas->aa) { + gdk_gc_unref (note_item->fill_gc); + note_item->fill_gc = NULL; + gdk_gc_unref (note_item->outline_gc); + note_item->outline_gc = NULL; + } + + if (note_item_parent_class->unrealize) + (* note_item_parent_class->unrealize) (item); +} + +static void +nautilus_canvas_note_item_translate (GnomeCanvasItem *item, double dx, double dy) +{ + NautilusCanvasNoteItem *note_item; + + note_item = NAUTILUS_CANVAS_NOTE_ITEM (item); + + note_item->x1 += dx; + note_item->y1 += dy; + note_item->x2 += dx; + note_item->y2 += dy; + + update_item_bounding_box (note_item); + + if (item->canvas->aa) { + gnome_canvas_item_request_update (item); + } +} + +static void +nautilus_canvas_note_item_bounds (GnomeCanvasItem *item, double *x1, double *y1, double *x2, double *y2) +{ + NautilusCanvasNoteItem *note_item; + double hwidth; + + note_item = NAUTILUS_CANVAS_NOTE_ITEM (item); + + if (note_item->width_pixels) + hwidth = (note_item->width / item->canvas->pixels_per_unit) / 2.0; + else + hwidth = note_item->width / 2.0; + + *x1 = note_item->x1 - hwidth; + *y1 = note_item->y1 - hwidth; + *x2 = note_item->x2 + hwidth; + *y2 = note_item->y2 + hwidth; +} + +/* utility routine to map raw annotation text into text to be displayed */ +/* for now this is pretty naive and only handles free-form text, just returning + * the first suitable annotation it can find + */ + +/* utility routine to draw a text string into the passed-in item */ +static void +draw_item_aa_text (GnomeCanvasBuf *buf, GnomeCanvasItem *item, const char *note_text) +{ + EelScalableFont *font; + GdkPixbuf *text_pixbuf; + ArtIRect item_bounds, dest_bounds; + int width, height; + EelSmoothTextLayout *smooth_text_layout; + + font = eel_scalable_font_get_default_font (); + + eel_gnome_canvas_item_get_canvas_bounds (item, &item_bounds); + width = item_bounds.x1 - item_bounds.x0; + height = item_bounds.y1 - item_bounds.y0; + + text_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, + TRUE, + 8, + width, + height); + eel_gdk_pixbuf_fill_rectangle_with_color (text_pixbuf, NULL, + EEL_RGBA_COLOR_PACK (0, 0, 0, 0)); + + smooth_text_layout = eel_smooth_text_layout_new ( + note_text, strlen(note_text), + font, DEFAULT_FONT_SIZE, TRUE); + + eel_smooth_text_layout_set_line_wrap_width (smooth_text_layout, width - 4); + + dest_bounds.x0 = 0; + dest_bounds.y0 = ARROW_HEIGHT; + dest_bounds.x1 = width; + dest_bounds.y1 = height; + + eel_smooth_text_layout_draw_to_pixbuf + (smooth_text_layout, text_pixbuf, + 0, 0, &dest_bounds, GTK_JUSTIFY_LEFT, + FALSE, EEL_RGBA_COLOR_OPAQUE_BLACK, + EEL_OPACITY_FULLY_OPAQUE); + + gtk_object_destroy (GTK_OBJECT (smooth_text_layout)); + + eel_gnome_canvas_draw_pixbuf (buf, text_pixbuf, item_bounds.x0 + 4, item_bounds.y0 + 2); + + gdk_pixbuf_unref (text_pixbuf); + gtk_object_unref (GTK_OBJECT (font)); +} + +static void +nautilus_canvas_note_item_render (GnomeCanvasItem *item, + GnomeCanvasBuf *buf) +{ + NautilusCanvasNoteItem *note_item; + char *display_text; + + note_item = NAUTILUS_CANVAS_NOTE_ITEM (item); + + if (note_item->fill_svp != NULL) { + gnome_canvas_render_svp (buf, note_item->fill_svp, note_item->fill_color); + } + + /* draw the annotation text, if necessary */ + if (note_item->note_text) { + display_text = nautilus_annotation_get_display_text (note_item->note_text); + if (display_text && strlen (display_text)) { + draw_item_aa_text (buf, item, display_text); + } + g_free (display_text); + } + + if (note_item->outline_svp != NULL) { + gnome_canvas_render_svp (buf, note_item->outline_svp, note_item->outline_color); + } +} + +static void +nautilus_canvas_note_item_draw (GnomeCanvasItem *item, GdkDrawable *drawable, int x, int y, int width, int height) +{ + NautilusCanvasNoteItem *note_item; + GdkFont *font; + char* display_text; + double i2w[6], w2c[6], i2c[6]; + int x1, y1, x2, y2; + ArtPoint i1, i2; + ArtPoint c1, c2; + GnomeIconTextInfo *text_info; + + note_item = NAUTILUS_CANVAS_NOTE_ITEM (item); + + /* Get canvas pixel coordinates */ + gnome_canvas_item_i2w_affine (item, i2w); + gnome_canvas_w2c_affine (item->canvas, w2c); + art_affine_multiply (i2c, i2w, w2c); + + i1.x = note_item->x1; + i1.y = note_item->y1; + i2.x = note_item->x2; + i2.y = note_item->y2; + art_affine_point (&c1, &i1, i2c); + art_affine_point (&c2, &i2, i2c); + x1 = c1.x; + y1 = c1.y; + x2 = c2.x; + y2 = c2.y; + + if (note_item->fill_stipple) + gnome_canvas_set_stipple_origin (item->canvas, note_item->fill_gc); + + gdk_draw_rectangle (drawable, + note_item->fill_gc, + TRUE, + x1 - x, + y1 - y, + x2 - x1 + 1, + y2 - y1 + 1); + + /* draw the annotation text */ + if (note_item->note_text) { + font = nautilus_font_factory_get_font_from_preferences (DEFAULT_FONT_SIZE); + display_text = nautilus_annotation_get_display_text (note_item->note_text); + + text_info = gnome_icon_layout_text + (font, display_text, + LINE_BREAK_CHARACTERS, + x2 - x1 - 2, TRUE); + + gnome_icon_paint_text (text_info, drawable, note_item->outline_gc, + x1 - x + 4, y1 - y + 4, GTK_JUSTIFY_LEFT); + gnome_icon_text_info_free (text_info); + + gdk_font_unref (font); + g_free (display_text); + } + + if (note_item->outline_stipple) + gnome_canvas_set_stipple_origin (item->canvas, note_item->outline_gc); + + gdk_draw_rectangle (drawable, + note_item->outline_gc, + FALSE, + x1 - x, + y1 - y, + x2 - x1, + y2 - y1); +} + +static double +nautilus_canvas_note_item_point (GnomeCanvasItem *item, double x, double y, int cx, int cy, GnomeCanvasItem **actual_item) +{ + NautilusCanvasNoteItem *note_item; + double x1, y1, x2, y2; + double hwidth; + double dx, dy; + + note_item = NAUTILUS_CANVAS_NOTE_ITEM (item); + + *actual_item = item; + + /* Find the bounds for the rectangle plus its outline width */ + + x1 = note_item->x1; + y1 = note_item->y1; + x2 = note_item->x2; + y2 = note_item->y2; + + if (note_item->width_pixels) + hwidth = (note_item->width / item->canvas->pixels_per_unit) / 2.0; + else + hwidth = note_item->width / 2.0; + + x1 -= hwidth; + y1 -= hwidth; + x2 += hwidth; + y2 += hwidth; + + /* Is point inside rectangle (which can be hollow if it has no fill set)? */ + + if ((x >= x1) && (y >= y1) && (x <= x2) && (y <= y2)) { + return 0.0; + } + + /* Point is outside rectangle */ + + if (x < x1) + dx = x1 - x; + else if (x > x2) + dx = x - x2; + else + dx = 0.0; + + if (y < y1) + dy = y1 - y; + else if (y > y2) + dy = y - y2; + else + dy = 0.0; + + return sqrt (dx * dx + dy * dy); +} + +static void +nautilus_canvas_note_item_update (GnomeCanvasItem *item, double affine[6], ArtSVP *clip_path, gint flags) +{ + NautilusCanvasNoteItem *note_item; + ArtVpath vpath[9]; + ArtVpath *vpath2; + ArtSVP *stroke_svp; + double x0, y0, x1, y1; + double round_off_amount; + double midpoint; + double arrow_half_width; + note_item = NAUTILUS_CANVAS_NOTE_ITEM (item); + + if (note_item_parent_class->update) + (* note_item_parent_class->update) (item, affine, clip_path, flags); + + if (item->canvas->aa) { + x0 = note_item->x1; + y0 = note_item->y1 + ARROW_HEIGHT; + x1 = note_item->x2; + y1 = note_item->y2; + + round_off_amount = item->canvas->pixels_per_unit / 2; + + gnome_canvas_item_reset_bounds (item); + midpoint = (x1 + x0) / 2; + arrow_half_width = (x1 - x0) / 16; + arrow_half_width = CLAMP (arrow_half_width, MIN_ARROW_HALF_WIDTH, MAX_ARROW_HALF_WIDTH); + + vpath[0].code = ART_MOVETO; + vpath[0].x = x0 - round_off_amount; + vpath[0].y = y0 - round_off_amount; + + vpath[1].code = ART_LINETO; + vpath[1].x = x0 - round_off_amount; + vpath[1].y = y1 + round_off_amount; + + vpath[2].code = ART_LINETO; + vpath[2].x = x1 + round_off_amount; + vpath[2].y = y1 + round_off_amount; + + vpath[3].code = ART_LINETO; + vpath[3].x = x1 + round_off_amount; + vpath[3].y = y0 - round_off_amount; + + vpath[4].code = ART_LINETO; + vpath[4].x = midpoint + arrow_half_width + round_off_amount; + vpath[4].y = y0 - round_off_amount; + + vpath[5].code = ART_LINETO; + vpath[5].x = midpoint + round_off_amount; + vpath[5].y = note_item->y1 - round_off_amount; + + vpath[6].code = ART_LINETO; + vpath[6].x = midpoint - arrow_half_width - round_off_amount; + vpath[6].y = y0 - round_off_amount; + + vpath[7].code = ART_LINETO; + vpath[7].x = x0 - round_off_amount; + vpath[7].y = y0 - round_off_amount; + + vpath[8].code = ART_END; + vpath[8].x = 0; + vpath[8].y = 0; + + vpath2 = art_vpath_affine_transform (vpath, affine); + + gnome_canvas_item_update_svp_clip (item, ¬e_item->fill_svp, art_svp_from_vpath (vpath2), clip_path); + + stroke_svp = art_svp_vpath_stroke (vpath2, + ART_PATH_STROKE_JOIN_MITER, + ART_PATH_STROKE_CAP_BUTT, + (note_item->width_pixels) ? note_item->width : (note_item->width * item->canvas->pixels_per_unit), + 4, + 25); + + gnome_canvas_item_update_svp_clip (item, ¬e_item->outline_svp, stroke_svp, clip_path); + art_free (vpath2); + + eel_gnome_canvas_item_request_redraw + (GNOME_CANVAS_ITEM (item)); + + } else { + /* xlib rendering - just update the bbox */ + + set_gc_foreground (note_item->fill_gc, note_item->fill_pixel); + set_gc_foreground (note_item->outline_gc, note_item->outline_pixel); + set_stipple (note_item->fill_gc, ¬e_item->fill_stipple, note_item->fill_stipple, TRUE); + set_stipple (note_item->outline_gc, ¬e_item->outline_stipple, note_item->outline_stipple, TRUE); + set_outline_gc_width (note_item); + + get_bounds (note_item, &x0, &y0, &x1, &y1); + gnome_canvas_update_bbox (item, x0, y0, x1, y1); + } +} + diff --git a/libnautilus-private/nautilus-canvas-note-item.h b/libnautilus-private/nautilus-canvas-note-item.h new file mode 100644 index 000000000..3c763c3df --- /dev/null +++ b/libnautilus-private/nautilus-canvas-note-item.h @@ -0,0 +1,90 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- + + nautilus-canvas-note-item.h: annotation canvas item for Nautilus + + Copyright (C) 2001 Eazel, Inc. + + 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. + + based on gnome_canvas_rect_item by Federico Mena Quintero + + Author: Andy Hertzfeld <andy@eazel.com> +*/ + +#ifndef NAUTILUS_CANVAS_NOTE_ITEM_H +#define NAUTILUS_CANVAS_NOTE_ITEM_H + +#include <libgnome/gnome-defs.h> +#include <libgnomeui/gnome-canvas.h> + +#include <libart_lgpl/art_svp.h> + +BEGIN_GNOME_DECLS + +#define NAUTILUS_TYPE_CANVAS_NOTE_ITEM (nautilus_canvas_note_item_get_type ()) +#define NAUTILUS_CANVAS_NOTE_ITEM(obj) (GTK_CHECK_CAST ((obj), NAUTILUS_TYPE_CANVAS_NOTE_ITEM, NautilusCanvasNoteItem)) +#define NAUTILUS_CANVAS_NOTE_ITEM_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), NAUTILUS_CANVAS_NOTE_ITEM, NautilusCanvasNoteItemClass)) +#define NAUTILUS_IS_CANVAS_NOTE_ITEM(obj) (GTK_CHECK_TYPE ((obj), NAUTILUS_TYPE_CANVAS_NOTE_ITEM)) +#define NAUTILUS_IS_CANVAS_NOTE_ITEM_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_CANVAS_NOTE_ITEM)) + + +typedef struct _NautilusCanvasNoteItem NautilusCanvasNoteItem; +typedef struct _NautilusCanvasNoteItemClass NautilusCanvasNoteItemClass; + +struct _NautilusCanvasNoteItem { + GnomeCanvasItem item; + + double x1; /* canvas coordinates of bounding box */ + double y1; + double x2; + double y2; + + double width; /* Outline width */ + + guint fill_color; /* Fill color, RGBA */ + guint outline_color; /* Outline color, RGBA */ + + gulong fill_pixel; /* Fill color */ + gulong outline_pixel; /* Outline color */ + + GdkBitmap *fill_stipple; /* Stipple for fill */ + GdkBitmap *outline_stipple; /* Stipple for outline */ + + GdkGC *fill_gc; /* GC for filling */ + GdkGC *outline_gc; /* GC for outline */ + + /* text message */ + char *note_text; /* text for annotation */ + + /* Antialiased specific stuff follows */ + ArtSVP *fill_svp; /* The SVP for the filled shape */ + ArtSVP *outline_svp; /* The SVP for the outline shape */ + + /* Configuration flags */ + unsigned int width_pixels : 1; /* Is outline width specified in pixels or units? */ +}; + +struct _NautilusCanvasNoteItemClass { + GnomeCanvasItemClass parent_class; +}; + +/* Standard Gtk function */ +GtkType nautilus_canvas_note_item_get_type (void); + + +END_GNOME_DECLS + +#endif diff --git a/libnautilus-private/nautilus-file-utilities.c b/libnautilus-private/nautilus-file-utilities.c index d015879cd..5f6c5c990 100644 --- a/libnautilus-private/nautilus-file-utilities.c +++ b/libnautilus-private/nautilus-file-utilities.c @@ -224,7 +224,7 @@ nautilus_get_user_main_directory (void) /* If this fails to create the directory, nautilus_application_startup will * notice and refuse to launch. */ - + /* install the default link sets */ nautilus_link_set_install (user_main_directory, "apps"); nautilus_link_set_install (user_main_directory, "home"); diff --git a/libnautilus-private/nautilus-file.c b/libnautilus-private/nautilus-file.c index 8bd2b7c23..e251032dc 100644 --- a/libnautilus-private/nautilus-file.c +++ b/libnautilus-private/nautilus-file.c @@ -25,6 +25,7 @@ #include <config.h> #include "nautilus-file.h" +#include "nautilus-annotation.h" #include "nautilus-directory-metafile.h" #include "nautilus-directory-notify.h" #include "nautilus-directory-private.h" @@ -1500,6 +1501,10 @@ prepend_automatic_emblem_names (NautilusFile *file, { /* Prepend in reverse order. */ + if (nautilus_annotation_has_annotation (file) > 0) { + names = g_list_prepend + (names, g_strdup (NAUTILUS_FILE_EMBLEM_ANNOTATION)); + } if (nautilus_file_is_in_trash (file)) { names = g_list_prepend (names, g_strdup (NAUTILUS_FILE_EMBLEM_NAME_TRASH)); diff --git a/libnautilus-private/nautilus-file.h b/libnautilus-private/nautilus-file.h index 8a4892969..820cae932 100644 --- a/libnautilus-private/nautilus-file.h +++ b/libnautilus-private/nautilus-file.h @@ -71,6 +71,7 @@ typedef enum { #define NAUTILUS_FILE_EMBLEM_NAME_CANT_READ "noread" #define NAUTILUS_FILE_EMBLEM_NAME_CANT_WRITE "nowrite" #define NAUTILUS_FILE_EMBLEM_NAME_TRASH "trash" +#define NAUTILUS_FILE_EMBLEM_ANNOTATION "note" typedef void (*NautilusFileCallback) (NautilusFile *file, gpointer callback_data); diff --git a/libnautilus-private/nautilus-global-preferences.c b/libnautilus-private/nautilus-global-preferences.c index c9d59c17a..5603d7dee 100644 --- a/libnautilus-private/nautilus-global-preferences.c +++ b/libnautilus-private/nautilus-global-preferences.c @@ -368,6 +368,18 @@ static const PreferenceDefault preference_defaults[] = { { NAUTILUS_USER_LEVEL_NOVICE, GINT_TO_POINTER (TRUE) }, { USER_LEVEL_NONE } }, + { NAUTILUS_PREFERENCES_LOOKUP_ANNOTATIONS, + PREFERENCE_BOOLEAN, + NAUTILUS_USER_LEVEL_INTERMEDIATE, + { NAUTILUS_USER_LEVEL_NOVICE, GINT_TO_POINTER (FALSE) }, + { USER_LEVEL_NONE } + }, + { NAUTILUS_PREFERENCES_DISPLAY_ANNOTATIONS, + PREFERENCE_BOOLEAN, + NAUTILUS_USER_LEVEL_INTERMEDIATE, + { NAUTILUS_USER_LEVEL_NOVICE, GINT_TO_POINTER (FALSE) }, + { USER_LEVEL_NONE } + }, { NAUTILUS_PREFERENCES_PREVIEW_SOUND, PREFERENCE_INTEGER, NAUTILUS_USER_LEVEL_INTERMEDIATE, @@ -654,6 +666,14 @@ global_preferences_install_defaults (void) preference_defaults[i].visible_user_level); } + nautilus_preferences_default_set_boolean (NAUTILUS_PREFERENCES_LOOKUP_ANNOTATIONS, + NAUTILUS_USER_LEVEL_NOVICE, + FALSE); + + nautilus_preferences_default_set_boolean (NAUTILUS_PREFERENCES_DISPLAY_ANNOTATIONS, + NAUTILUS_USER_LEVEL_NOVICE, + FALSE); + /* Add the gnome-vfs path to the list of monitored directories - for proxy settings */ nautilus_preferences_monitor_directory (SYSTEM_GNOME_VFS_PATH); @@ -1041,6 +1061,24 @@ static PreferenceDialogItem tradeoffs_items[] = { { NULL } }; +static PreferenceDialogItem annotation_items[] = { + { N_("Lookup File Annotations"), + NAUTILUS_PREFERENCES_LOOKUP_ANNOTATIONS, + N_("Use the service to lookup file annotations"), + NAUTILUS_PREFERENCE_ITEM_BOOLEAN, + NULL, + 0 + }, + { N_("Display File Annotations"), + NAUTILUS_PREFERENCES_DISPLAY_ANNOTATIONS, + N_("Display emblems for file annotations"), + NAUTILUS_PREFERENCE_ITEM_BOOLEAN, + NULL, + 0 + }, + { NULL, NULL, NULL, 0, NULL, 0 } +}; + static GtkWidget * global_preferences_create_dialog (void) { @@ -1105,6 +1143,11 @@ global_preferences_create_dialog (void) _("Speed Tradeoffs"), tradeoffs_items); + /* Annotations */ + global_preferences_populate_pane (preference_box, + _("Annotations"), + annotation_items); + /* Update the dialog so that the right items show up based on the current user level */ nautilus_preferences_dialog_update (NAUTILUS_PREFERENCES_DIALOG (prefs_dialog)); diff --git a/libnautilus-private/nautilus-global-preferences.h b/libnautilus-private/nautilus-global-preferences.h index d52cd42fe..fdaac1e2b 100644 --- a/libnautilus-private/nautilus-global-preferences.h +++ b/libnautilus-private/nautilus-global-preferences.h @@ -110,6 +110,10 @@ enum NAUTILUS_DEFAULT_FOLDER_VIEWER_LIST_VIEW }; +/* enabling annotations */ +#define NAUTILUS_PREFERENCES_LOOKUP_ANNOTATIONS "preferences/lookup_annotations" +#define NAUTILUS_PREFERENCES_DISPLAY_ANNOTATIONS "preferences/display_annotations" + /* Icon View */ #define NAUTILUS_PREFERENCES_ICON_VIEW_DEFAULT_SORT_IN_REVERSE_ORDER "icon-view/default_sort_in_reverse_order" #define NAUTILUS_PREFERENCES_ICON_VIEW_DEFAULT_SORT_ORDER "icon-view/default_sort_order" diff --git a/libnautilus-private/nautilus-icon-canvas-item.c b/libnautilus-private/nautilus-icon-canvas-item.c index 2e9c11bb6..58b729a95 100644 --- a/libnautilus-private/nautilus-icon-canvas-item.c +++ b/libnautilus-private/nautilus-icon-canvas-item.c @@ -3,7 +3,6 @@ /* Nautilus - Icon canvas item class for icon container. * * Copyright (C) 2000 Eazel, Inc - * * Author: Andy Hertzfeld <andy@eazel.com> * * This library is free software; you can redistribute it and/or @@ -29,8 +28,10 @@ #include <string.h> #include <stdio.h> #include <gtk/gtksignal.h> +#include <gtk/gtkmain.h> #include <gdk-pixbuf/gdk-pixbuf.h> #include <libgnome/gnome-i18n.h> +#include <libgnomeui/gnome-canvas-rect-ellipse.h> #include <libgnomeui/gnome-canvas-util.h> #include <libgnomeui/gnome-icon-text.h> #include <libart_lgpl/art_rgb.h> @@ -38,6 +39,7 @@ #include <libart_lgpl/art_rgb_rgba_affine.h> #include <libart_lgpl/art_svp_vpath.h> #include "nautilus-icon-private.h" +#include "nautilus-canvas-note-item.h" #include <eel/eel-string.h> #include <eel/eel-art-extensions.h> #include <eel/eel-glib-extensions.h> @@ -54,9 +56,6 @@ #include <eel/eel-smooth-text-layout.h> #include <eel/eel-smooth-text-layout-cache.h> -/* Comment this out if the new smooth fonts code give you problems - * This isnt meant to be permanent. Its just a precaution. - */ #define EMBLEM_SPACING 2 /* gap between bottom of icon and start of text box */ @@ -80,6 +79,17 @@ struct NautilusIconCanvasItemDetails { GdkFont *font; NautilusEmblemAttachPoints *attach_points; + /* stuff for controls; if this gets too big, we'll put it in a separate struct */ + GtkWidget *control; /* optional Bonobo control*/ + guint control_destroy_id; + + /* stuff for annotations - since these are infrequently used, we probably should + * combine them into a structure so we only use a pointer for every item eventually + */ + GnomeCanvasItem *annotation; + int annotation_time_out; + int note_state; + /* Size of the text at current font. */ int text_width; int text_height; @@ -93,7 +103,7 @@ struct NautilusIconCanvasItemDetails { guint is_highlighted_for_drop : 1; guint show_stretch_handles : 1; guint is_prelit : 1; - + guint in_control_destroy : 1; gboolean is_renaming; /* Font stuff whilst in smooth mode */ @@ -112,7 +122,7 @@ enum { ARG_EDITABLE_TEXT, ARG_ADDITIONAL_TEXT, ARG_FONT, - ARG_HIGHLIGHTED_FOR_SELECTION, + ARG_HIGHLIGHTED_FOR_SELECTION, ARG_HIGHLIGHTED_AS_KEYBOARD_FOCUS, ARG_HIGHLIGHTED_FOR_DROP, ARG_MODIFIER, @@ -210,9 +220,13 @@ static void get_icon_canvas_rectangle (NautilusIconCanvasIt static void emblem_layout_reset (EmblemLayout *layout, NautilusIconCanvasItem *icon_item, const ArtIRect *icon_rect); -static gboolean emblem_layout_next (EmblemLayout *layout, +static gboolean emblem_layout_next (EmblemLayout *layout, GdkPixbuf **emblem_pixbuf, ArtIRect *emblem_rect); +static void get_emblem_rectangle (NautilusIconCanvasItem *icon_item, + int which_emblem, + ArtIRect *rect); + static void draw_pixbuf (GdkPixbuf *pixbuf, GdkDrawable *drawable, int x, @@ -222,7 +236,6 @@ static gboolean hit_test_stretch_handle (NautilusIconCanvasIt static gboolean icon_canvas_item_is_smooth (const NautilusIconCanvasItem *icon_item); - EEL_DEFINE_CLASS_BOILERPLATE (NautilusIconCanvasItem, nautilus_icon_canvas_item, GNOME_TYPE_CANVAS_ITEM) static EelSmoothTextLayoutCache *layout_cache; @@ -308,7 +321,8 @@ nautilus_icon_canvas_item_initialize (NautilusIconCanvasItem *icon_item) /* set up the default font and size */ icon_item->details->smooth_font_size = 12; - icon_item->details->smooth_font = eel_scalable_font_get_default_font (); + icon_item->details->smooth_font = eel_scalable_font_get_default_font (); + icon_item->details->annotation_time_out = -1; } /* Destroy handler for the icon canvas item. */ @@ -339,6 +353,11 @@ nautilus_icon_canvas_item_destroy (GtkObject *object) gdk_font_unref (details->font); } + if (details->control && !details->in_control_destroy) { + gtk_signal_disconnect (GTK_OBJECT (details->control), details->control_destroy_id); + gtk_widget_destroy (details->control); + } + gtk_object_unref (GTK_OBJECT (icon_item->details->smooth_font)); icon_item->details->smooth_font = NULL; @@ -370,6 +389,43 @@ nautilus_icon_canvas_item_invalidate_label_size (NautilusIconCanvasItem *item) item->details->text_height = -1; } +/* abstraction layer for icon width and height, to separate it from pixbuf with and height */ +static int +nautilus_icon_canvas_item_get_icon_width (NautilusIconCanvasItem *item) +{ + GtkRequisition size_requisition; + double scale_factor = GNOME_CANVAS_ITEM (item)->canvas->pixels_per_unit; + + if (item->details->control != NULL) { + gtk_widget_size_request (item->details->control, &size_requisition); + return size_requisition.width * scale_factor; + } + + if (item->details->pixbuf == NULL) { + return NAUTILUS_ICON_SIZE_STANDARD; + } + + return gdk_pixbuf_get_width (item->details->pixbuf); +} + +static int +nautilus_icon_canvas_item_get_icon_height (NautilusIconCanvasItem *item) +{ + GtkRequisition size_requisition; + double scale_factor = GNOME_CANVAS_ITEM (item)->canvas->pixels_per_unit; + + if (item->details->control != NULL) { + gtk_widget_size_request (item->details->control, &size_requisition); + return size_requisition.height * scale_factor; + } + if (item->details->pixbuf == NULL) { + return NAUTILUS_ICON_SIZE_STANDARD; + } + + return gdk_pixbuf_get_height (item->details->pixbuf); +} + + /* Set_arg handler for the icon item. */ static void nautilus_icon_canvas_item_set_arg (GtkObject *object, GtkArg *arg, guint arg_id) @@ -452,8 +508,8 @@ nautilus_icon_canvas_item_set_arg (GtkObject *object, GtkArg *arg, guint arg_id) case ARG_SMOOTH_FONT_SIZE: nautilus_icon_canvas_item_set_smooth_font_size (NAUTILUS_ICON_CANVAS_ITEM (object), GTK_VALUE_INT (*arg)); - break; - + break; + default: g_warning ("nautilus_icons_view_item_item_set_arg on unknown argument"); return; @@ -462,12 +518,27 @@ nautilus_icon_canvas_item_set_arg (GtkObject *object, GtkArg *arg, guint arg_id) gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (object)); } +/* handler for the control's destroy signal */ +static void +do_control_destroy (GtkObject *object, gpointer data) +{ + NautilusIconCanvasItemDetails *details; + + details = NAUTILUS_ICON_CANVAS_ITEM (data)->details; + + details->in_control_destroy = TRUE; + + gtk_object_destroy (GTK_OBJECT (data)); +} + /* Get_arg handler for the icon item */ static void nautilus_icon_canvas_item_get_arg (GtkObject *object, GtkArg *arg, guint arg_id) { NautilusIconCanvasItemDetails *details; + GnomeCanvasItem *item; + item = GNOME_CANVAS_ITEM (object); details = NAUTILUS_ICON_CANVAS_ITEM (object)->details; switch (arg_id) { @@ -515,12 +586,27 @@ GdkPixbuf * nautilus_icon_canvas_item_get_image (NautilusIconCanvasItem *item) { NautilusIconCanvasItemDetails *details; - + int width, height; + GdkPixbuf *pixbuf; + g_return_val_if_fail (NAUTILUS_IS_ICON_CANVAS_ITEM (item), NULL); details = item->details; - return details->pixbuf; + if (details->control) { + width = details->control->allocation.width; + height = details->control->allocation.height; + pixbuf = eel_gdk_pixbuf_get_from_window_safe (details->control->window, + details->control->allocation.x, + details->control->allocation.y, + details->control->allocation.width, + details->control->allocation.height); + } else { + pixbuf = details->pixbuf; + gdk_pixbuf_ref (pixbuf); + } + + return pixbuf; } void @@ -622,6 +708,12 @@ recompute_bounding_box (NautilusIconCanvasItem *icon_item) item->y1 = top_left.y; item->x2 = bottom_right.x; item->y2 = bottom_right.y; + + if (icon_item->details->control) + gtk_layout_move (GTK_LAYOUT (item->canvas), icon_item->details->control, + item->x1 + item->canvas->zoom_xofs, + item->y1 + item->canvas->zoom_yofs); + } static void @@ -636,13 +728,16 @@ compute_text_rectangle (NautilusIconCanvasItem *item, text_rect->y1 = text_rect->y0 + item->details->text_height; } + void nautilus_icon_canvas_item_update_bounds (NautilusIconCanvasItem *item) { ArtIRect before, after, emblem_rect; EmblemLayout emblem_layout; GdkPixbuf *emblem_pixbuf; - + GtkRequisition size_requisition; + int item_width, item_height; + /* Compute new bounds. */ eel_gnome_canvas_item_get_current_canvas_bounds (GNOME_CANVAS_ITEM (item), &before); @@ -669,6 +764,16 @@ nautilus_icon_canvas_item_update_bounds (NautilusIconCanvasItem *item) art_irect_union (&item->details->emblem_rect, &item->details->emblem_rect, &emblem_rect); } + /* if there is an embedded control, make a size request and size accordingly */ + if (item->details->control) { + /* size the control appropriately */ + gtk_widget_size_request (item->details->control, &size_requisition); + item_width = size_requisition.width * GNOME_CANVAS_ITEM (item)->canvas->pixels_per_unit; + item_height = size_requisition.height * GNOME_CANVAS_ITEM (item)->canvas->pixels_per_unit; + + gtk_widget_set_usize (item->details->control, item_width, item_height); + } + /* Send out the bounds_changed signal and queue a redraw. */ eel_gnome_canvas_request_redraw_rectangle (GNOME_CANVAS_ITEM (item)->canvas, &before); @@ -809,7 +914,7 @@ draw_or_measure_label_text (NautilusIconCanvasItem *item, canvas_item = GNOME_CANVAS_ITEM (item); if (drawable != NULL) { - icon_width = details->pixbuf == NULL ? 0 : gdk_pixbuf_get_width (details->pixbuf); + icon_width = details->pixbuf == NULL ? 0 : nautilus_icon_canvas_item_get_icon_width (item); gc = gdk_gc_new (canvas_item->canvas->layout.bin_window); gdk_gc_get_values (gc, &save_gc); } @@ -1125,6 +1230,7 @@ emblem_layout_next (EmblemLayout *layout, /* Advance to the next emblem. */ layout->emblem = layout->emblem->next; + layout->index += 1; attach_points = layout->icon_item->details->attach_points; if (attach_points != NULL) { @@ -1135,8 +1241,6 @@ emblem_layout_next (EmblemLayout *layout, x = layout->icon_rect.x0 + attach_points->points[layout->index].x; y = layout->icon_rect.y0 + attach_points->points[layout->index].y; - layout->index += 1; - /* Return the rectangle and pixbuf. */ *emblem_pixbuf = pixbuf; emblem_rect->x0 = x - width / 2; @@ -1211,11 +1315,17 @@ emblem_layout_next (EmblemLayout *layout, /* Return the rectangle and pixbuf. */ *emblem_pixbuf = pixbuf; - emblem_rect->x0 = x - width / 2; - emblem_rect->y0 = y - height / 2; + if (layout->icon_item->details->control) { + emblem_rect->x0 = x; + emblem_rect->y0 = y; + } else { + emblem_rect->x0 = x - width / 2; + emblem_rect->y0 = y - height / 2; + } + emblem_rect->x1 = emblem_rect->x0 + width; emblem_rect->y1 = emblem_rect->y0 + height; - + return TRUE; } @@ -1330,10 +1440,6 @@ nautilus_icon_canvas_item_draw (GnomeCanvasItem *item, GdkDrawable *drawable, icon_item = NAUTILUS_ICON_CANVAS_ITEM (item); details = icon_item->details; - /* Draw the pixbuf. */ - if (details->pixbuf == NULL) { - return; - } /* Compute icon rectangle in drawable coordinates. */ icon_rect = icon_item->details->canvas_rect; @@ -1341,12 +1447,29 @@ nautilus_icon_canvas_item_draw (GnomeCanvasItem *item, GdkDrawable *drawable, icon_rect.y0 -= y; icon_rect.x1 -= x; icon_rect.y1 -= y; + /* draw the icon or widget */ + if (icon_item->details->control) { + gtk_widget_queue_draw (icon_item->details->control); + } else { + if (details->pixbuf != NULL) { + + /* Compute icon rectangle in drawable coordinates. */ + get_icon_canvas_rectangle (icon_item, &icon_rect); + icon_rect.x0 -= x; + icon_rect.y0 -= y; + icon_rect.x1 -= x; + icon_rect.y1 -= y; + + /* if the pre-lit or selection flag is set, make a pre-lit or darkened pixbuf and draw that instead */ + temp_pixbuf = map_pixbuf (icon_item); + draw_pixbuf (temp_pixbuf, drawable, icon_rect.x0, icon_rect.y0); + + if (temp_pixbuf != details->pixbuf) { + gdk_pixbuf_unref (temp_pixbuf); + } + + } - /* if the pre-lit or selection flag is set, make a pre-lit or darkened pixbuf and draw that instead */ - temp_pixbuf = map_pixbuf (icon_item); - draw_pixbuf (temp_pixbuf, drawable, icon_rect.x0, icon_rect.y0); - if (temp_pixbuf != details->pixbuf) { - gdk_pixbuf_unref (temp_pixbuf); } /* Draw the emblem pixbufs. */ @@ -1431,7 +1554,7 @@ draw_or_measure_label_text_aa (NautilusIconCanvasItem *item, if (destination_pixbuf == NULL ) { icon_width = 0; } else { - icon_width = details->pixbuf == NULL ? 0 : gdk_pixbuf_get_width (details->pixbuf); + icon_width = details->pixbuf == NULL ? 0 : nautilus_icon_canvas_item_get_icon_width (item); } max_text_width = floor (nautilus_icon_canvas_item_get_max_text_width (item)); @@ -1706,15 +1829,21 @@ nautilus_icon_canvas_item_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf) gnome_canvas_buf_ensure_buf (buf); buf->is_bg = FALSE; } - - /* draw the icon */ - eel_gnome_canvas_draw_pixbuf (buf, temp_pixbuf, icon_rect.x0, icon_rect.y0); + + /* draw the icon or widget */ + if (icon_item->details->control) { + gtk_widget_queue_draw (icon_item->details->control); + } else { + eel_gnome_canvas_draw_pixbuf (buf, temp_pixbuf, icon_rect.x0, icon_rect.y0); + } if (temp_pixbuf != icon_item->details->pixbuf) { gdk_pixbuf_unref (temp_pixbuf); } - - /* draw the emblems */ + + /* draw the emblems */ + get_icon_canvas_rectangle (icon_item, &icon_rect); + emblem_layout_reset (&emblem_layout, icon_item, &icon_rect); while (emblem_layout_next (&emblem_layout, &emblem_pixbuf, &emblem_rect)) { eel_gnome_canvas_draw_pixbuf (buf, emblem_pixbuf, emblem_rect.x0, emblem_rect.y0); @@ -1728,6 +1857,124 @@ nautilus_icon_canvas_item_render (GnomeCanvasItem *item, GnomeCanvasBuf *buf) draw_label_text_aa (icon_item, buf, icon_rect.x0, icon_rect.y1, x_delta); } +/* create an annotation for the emblem designated by the passed-in index */ +static void +create_annotation (NautilusIconCanvasItem *icon_item, int emblem_index) +{ + uint fill_color, outline_color; + double top, left; + double right, bottom; + ArtDRect icon_rect; + ArtIRect emblem_rect; + int emblem_x, emblem_y; + double world_emblem_x, world_emblem_y; + int annotation_width; + char *note_text; + GnomeCanvas *canvas; + GnomeCanvasItem *item; + + /* compute the position for the top left of the annotation */ + nautilus_icon_canvas_item_get_icon_rectangle (icon_item, &icon_rect); + left = icon_rect.x0 + 8.0; + top = icon_rect.y0 + 8.0; + + fill_color = 0xFFFF75E0; + outline_color = 0x000000FF; + + canvas = GNOME_CANVAS_ITEM (icon_item)->canvas; + item = GNOME_CANVAS_ITEM (icon_item); + + note_text = nautilus_icon_container_get_note_text (NAUTILUS_ICON_CONTAINER (canvas), icon_item->user_data, emblem_index); + + icon_item->details->annotation = gnome_canvas_item_new + (gnome_canvas_root (canvas), + nautilus_canvas_note_item_get_type (), + "x1", left, + "y1", top, + "fill_color_rgba", fill_color, + "outline_color_rgba", outline_color, + "note_text", note_text, + "width_pixels", 1, + NULL); + + g_free (note_text); + + /* reposition the item now that it's had a chance to be properly sized */ + if (canvas->aa) { + get_emblem_rectangle (icon_item, emblem_index, &emblem_rect); + annotation_width = icon_item->details->annotation->x2 - icon_item->details->annotation->x1; + + emblem_x = (emblem_rect.x0 + emblem_rect.x1) / 2; + emblem_y = (emblem_rect.y0 + emblem_rect.y1) / 2; + gnome_canvas_c2w (canvas, emblem_x, emblem_y, &world_emblem_x, &world_emblem_y); + + left = world_emblem_x - (annotation_width / 2.0 ); + top = world_emblem_y; + right = left + annotation_width; + bottom = top + icon_item->details->annotation->y2 - icon_item->details->annotation->y1; + + gnome_canvas_item_set (icon_item->details->annotation, "x1", left, "y1", top, "x2", right, "y2", bottom, NULL); + } + + gnome_canvas_item_raise_to_top (icon_item->details->annotation); +} + +/* remove any annotation that's showing */ +static void +remove_annotation (NautilusIconCanvasItem *icon_item) +{ + if (icon_item->details->annotation != NULL) { + gtk_object_destroy (GTK_OBJECT (icon_item->details->annotation)); + icon_item->details->annotation = NULL; + icon_item->details->note_state = 0; + } +} + +/* handle the timeout firing by creating the annotation */ +static int +create_annotation_timeout_callback (gpointer callback_data) +{ + NautilusIconCanvasItem *icon_item; + + icon_item = NAUTILUS_ICON_CANVAS_ITEM (callback_data); + create_annotation (icon_item, icon_item->details->note_state); + + icon_item->details->annotation_time_out = -1; + return 0; +} + +/* manage showing and hiding annotations, based on mouse-over the passed-in emblem */ +static void +nautilus_icon_canvas_item_set_note_state (NautilusIconCanvasItem *icon_item, int new_state) +{ + /* nothing to do if nothing changed */ + if (new_state == icon_item->details->note_state) { + return; + } + + /* if there already is a timeout in progress and we're showing one, just wait for it to fire */ + if (new_state > 0 && icon_item->details->annotation_time_out >= 0) { + return; + } + + /* get rid of the old annotation, if there was one */ + if (icon_item->details->annotation) { + remove_annotation (icon_item); + } + + if (icon_item->details->annotation_time_out >= 0) { + gtk_timeout_remove (icon_item->details->annotation_time_out); + icon_item->details->annotation_time_out = -1; + } + + icon_item->details->note_state = new_state; + + /* add a timeout to create the new annotation */ + if (new_state > 0) { + icon_item->details->annotation_time_out = gtk_timeout_add (750, create_annotation_timeout_callback, icon_item); + } +} + /* handle events */ @@ -1735,7 +1982,12 @@ static int nautilus_icon_canvas_item_event (GnomeCanvasItem *item, GdkEvent *event) { NautilusIconCanvasItem *icon_item; - + GdkEventMotion *motion_event; + ArtIRect hit_rect; + ArtDRect world_rect; + HitType hit_type; + int hit_index, emblem_state; + icon_item = NAUTILUS_ICON_CANVAS_ITEM (item); switch (event->type) { @@ -1782,10 +2034,29 @@ nautilus_icon_canvas_item_event (GnomeCanvasItem *item, GdkEvent *event) icon_item->details->is_prelit = FALSE; icon_item->details->is_active = 0; icon_item->details->is_highlighted_for_drop = FALSE; + + nautilus_icon_canvas_item_set_note_state (icon_item, 0); gnome_canvas_item_request_update (item); } return TRUE; + + case GDK_MOTION_NOTIFY: + motion_event = (GdkEventMotion*) event; + + world_rect.x0 = motion_event->x; + world_rect.y0 = motion_event->y; + world_rect.x1 = world_rect.x0 + 1.0; + world_rect.y1 = world_rect.y0 + 1.0; + + eel_gnome_canvas_world_to_canvas_rectangle + (GNOME_CANVAS_ITEM (item)->canvas, &world_rect, &hit_rect); + /* hit-test so we can handle tooltips for emblems */ + nautilus_icon_canvas_item_hit_test_full (icon_item, &hit_rect, &hit_type, &hit_index); + emblem_state = hit_type == EMBLEM_HIT ? hit_index : 0; + nautilus_icon_canvas_item_set_note_state (icon_item, emblem_state); + return TRUE; + default: /* Don't eat up other events; icon container might use them. */ return FALSE; @@ -1838,10 +2109,14 @@ hit_test_pixbuf (GdkPixbuf *pixbuf, const ArtIRect *pixbuf_location, const ArtIR return FALSE; } -static gboolean -hit_test (NautilusIconCanvasItem *icon_item, const ArtIRect *canvas_rect) +gboolean +nautilus_icon_canvas_item_hit_test_full (NautilusIconCanvasItem *icon_item, + const ArtIRect *canvas_rect, + HitType *hit_type, + int *hit_index) { NautilusIconCanvasItemDetails *details; + ArtIRect icon_rect; ArtIRect emblem_rect; EmblemLayout emblem_layout; GdkPixbuf *emblem_pixbuf; @@ -1855,12 +2130,40 @@ hit_test (NautilusIconCanvasItem *icon_item, const ArtIRect *canvas_rect) return FALSE; } + /* default to -1, which means nothing was hit */ + if (hit_index != NULL) { + *hit_index = -1; + } + /* Check for hits in the stretch handles. */ if (hit_test_stretch_handle (icon_item, canvas_rect)) { + if (hit_type != NULL) { + *hit_type = STRETCH_HANDLE_HIT; + } return TRUE; } /* Check for hit in the icon. If we're highlighted for dropping, anywhere in the rect is OK */ + get_icon_canvas_rectangle (icon_item, &icon_rect); + + /* Check for hit in the emblem pixbufs first, since they appear on top of the icon. */ + emblem_layout_reset (&emblem_layout, icon_item, &icon_item->details->canvas_rect); + while (emblem_layout_next (&emblem_layout, &emblem_pixbuf, &emblem_rect)) { + if (hit_test_pixbuf (emblem_pixbuf, &emblem_rect, canvas_rect)) { + if (hit_type != NULL) { + *hit_type = EMBLEM_HIT; + } + if (hit_index != NULL) { + *hit_index = emblem_layout.index; + } + return TRUE; + } + } + + if (hit_type != NULL) { + *hit_type = ICON_HIT; + } + if (icon_item->details->is_highlighted_for_drop) { if (eel_art_irect_hits_irect (&icon_item->details->canvas_rect, canvas_rect)) { return TRUE; @@ -1874,17 +2177,17 @@ hit_test (NautilusIconCanvasItem *icon_item, const ArtIRect *canvas_rect) /* Check for hit in the text. */ if (eel_art_irect_hits_irect (&details->text_rect, canvas_rect) && !icon_item->details->is_renaming) { + if (hit_type != NULL) { + *hit_type = LABEL_HIT; + } return TRUE; } - /* Check for hit in the emblem pixbufs. */ - emblem_layout_reset (&emblem_layout, icon_item, &icon_item->details->canvas_rect); - while (emblem_layout_next (&emblem_layout, &emblem_pixbuf, &emblem_rect)) { - if (hit_test_pixbuf (emblem_pixbuf, &emblem_rect, canvas_rect)) { - return TRUE; - } + /* there wasn't a hit, so indicate that */ + if (hit_type != NULL) { + *hit_type = NO_HIT; } - + return FALSE; } @@ -1900,7 +2203,7 @@ nautilus_icon_canvas_item_point (GnomeCanvasItem *item, double x, double y, int canvas_rect.y0 = cy; canvas_rect.x1 = cx + 1; canvas_rect.y1 = cy + 1; - if (hit_test (NAUTILUS_ICON_CANVAS_ITEM (item), &canvas_rect)) { + if (nautilus_icon_canvas_item_hit_test_rectangle (NAUTILUS_ICON_CANVAS_ITEM (item), &canvas_rect)) { return 0.0; } else { /* This value means not hit. @@ -1939,8 +2242,8 @@ nautilus_icon_canvas_item_bounds (GnomeCanvasItem *item, icon_rect.x1 = 0; icon_rect.y1 = 0; } else { - icon_rect.x1 = gdk_pixbuf_get_width (details->pixbuf); - icon_rect.y1 = gdk_pixbuf_get_height (details->pixbuf); + icon_rect.x1 = nautilus_icon_canvas_item_get_icon_width (icon_item); + icon_rect.y1 = nautilus_icon_canvas_item_get_icon_height (icon_item); } /* Compute text rectangle. */ @@ -1986,10 +2289,32 @@ nautilus_icon_canvas_item_get_icon_rectangle (NautilusIconCanvasItem *item, pixbuf = item->details->pixbuf; pixels_per_unit = GNOME_CANVAS_ITEM (item)->canvas->pixels_per_unit; - rect->x1 = rect->x0 + (pixbuf == NULL ? 0 : gdk_pixbuf_get_width (pixbuf)) / pixels_per_unit; - rect->y1 = rect->y0 + (pixbuf == NULL ? 0 : gdk_pixbuf_get_height (pixbuf)) / pixels_per_unit; + rect->x1 = rect->x0 + (pixbuf == NULL ? 0 : nautilus_icon_canvas_item_get_icon_width (item)) / pixels_per_unit; + rect->y1 = rect->y0 + (pixbuf == NULL ? 0 : nautilus_icon_canvas_item_get_icon_height (item)) / pixels_per_unit; } +static void +get_emblem_rectangle (NautilusIconCanvasItem *icon_item, + int which_emblem, + ArtIRect *rect) +{ + EmblemLayout emblem_layout; + GdkPixbuf *pixbuf; + int emblem_index; + + emblem_layout_reset (&emblem_layout, icon_item, &icon_item->details->canvas_rect); + emblem_index = 0; + + rect->x0 = 0; + rect->y0 = 0; + rect->x1 = 0; + rect->y1 = 0; + + while (emblem_index < which_emblem && emblem_layout_next (&emblem_layout, &pixbuf, rect)) { + emblem_index += 1; + } +} + /* Get the rectangle of the icon only, in canvas coordinates. */ static void get_icon_canvas_rectangle (NautilusIconCanvasItem *item, @@ -2013,8 +2338,8 @@ get_icon_canvas_rectangle (NautilusIconCanvasItem *item, pixbuf = item->details->pixbuf; - rect->x1 = rect->x0 + (pixbuf == NULL ? 0 : gdk_pixbuf_get_width (pixbuf)); - rect->y1 = rect->y0 + (pixbuf == NULL ? 0 : gdk_pixbuf_get_height (pixbuf)); + rect->x1 = rect->x0 + (pixbuf == NULL ? 0 : nautilus_icon_canvas_item_get_icon_width (item)); + rect->y1 = rect->y0 + (pixbuf == NULL ? 0 : nautilus_icon_canvas_item_get_icon_height (item)); } void @@ -2110,10 +2435,11 @@ nautilus_icon_canvas_item_hit_test_stretch_handles (NautilusIconCanvasItem *item gboolean nautilus_icon_canvas_item_hit_test_rectangle (NautilusIconCanvasItem *item, const ArtIRect *canvas_rect) { + g_return_val_if_fail (NAUTILUS_IS_ICON_CANVAS_ITEM (item), FALSE); g_return_val_if_fail (canvas_rect != NULL, FALSE); - return hit_test (item, canvas_rect); + return nautilus_icon_canvas_item_hit_test_full (item, canvas_rect, NULL, NULL); } const char * @@ -2171,6 +2497,41 @@ nautilus_icon_canvas_item_set_smooth_font (NautilusIconCanvasItem *icon_item, } } +GtkWidget * +nautilus_icon_canvas_item_get_control (NautilusIconCanvasItem *icon_item) +{ + return icon_item->details->control; +} + +void +nautilus_icon_canvas_item_set_control (NautilusIconCanvasItem *icon_item, GtkWidget *control) +{ + GnomeCanvasItem *item; + + if (icon_item->details->control == control) { + return; + } + + item = GNOME_CANVAS_ITEM (icon_item); + if (icon_item->details->control) { + gtk_signal_disconnect (GTK_OBJECT (icon_item->details->control), icon_item->details->control_destroy_id); + gtk_container_remove (GTK_CONTAINER (item->canvas), icon_item->details->control); + icon_item->details->control = NULL; + } + + if (control) { + icon_item->details->control = control; + icon_item->details->control_destroy_id = gtk_signal_connect (GTK_OBJECT (control), + "destroy", + (GtkSignalFunc) do_control_destroy, + item); + gtk_widget_show (control); + gtk_layout_put (GTK_LAYOUT (item->canvas), control, + item->x1 + item->canvas->zoom_xofs, + item->y1 + item->canvas->zoom_yofs); + } +} + void nautilus_icon_canvas_item_set_smooth_font_size (NautilusIconCanvasItem *icon_item, int font_size) diff --git a/libnautilus-private/nautilus-icon-canvas-item.h b/libnautilus-private/nautilus-icon-canvas-item.h index f7be68e70..2f9f90a34 100644 --- a/libnautilus-private/nautilus-icon-canvas-item.h +++ b/libnautilus-private/nautilus-icon-canvas-item.h @@ -44,6 +44,14 @@ BEGIN_GNOME_DECLS #define NAUTILUS_IS_ICON_CANVAS_ITEM_CLASS(klass) \ (GTK_CHECK_CLASS_TYPE ((klass), NAUTILUS_TYPE_ICON_CANVAS_ITEM)) +typedef enum { + NO_HIT, + ICON_HIT, + LABEL_HIT, + STRETCH_HANDLE_HIT, + EMBLEM_HIT +} HitType; + typedef struct NautilusIconCanvasItem NautilusIconCanvasItem; typedef struct NautilusIconCanvasItemClass NautilusIconCanvasItemClass; typedef struct NautilusIconCanvasItemDetails NautilusIconCanvasItemDetails; @@ -80,10 +88,19 @@ const char *nautilus_icon_canvas_item_get_editable_text (NautilusIconCanv void nautilus_icon_canvas_item_set_renaming (NautilusIconCanvasItem *icon_item, gboolean state); +GtkWidget * nautilus_icon_canvas_item_get_control (NautilusIconCanvasItem *icon_item); +void nautilus_icon_canvas_item_set_control (NautilusIconCanvasItem *icon_item, + GtkWidget *control); + /* geometry and hit testing */ gboolean nautilus_icon_canvas_item_hit_test_rectangle (NautilusIconCanvasItem *item, const ArtIRect *canvas_rect); +gboolean nautilus_icon_canvas_item_hit_test_full (NautilusIconCanvasItem *icon_item, + const ArtIRect *canvas_rect, + HitType *hit_type, + int *hit_index); + gboolean nautilus_icon_canvas_item_hit_test_stretch_handles (NautilusIconCanvasItem *item, const ArtPoint *world_point); void nautilus_icon_canvas_item_invalidate_label_size (NautilusIconCanvasItem *item); diff --git a/libnautilus-private/nautilus-icon-container.c b/libnautilus-private/nautilus-icon-container.c index 9daecc782..e41f03522 100644 --- a/libnautilus-private/nautilus-icon-container.c +++ b/libnautilus-private/nautilus-icon-container.c @@ -119,10 +119,12 @@ enum { */ }; -static void activate_selected_items (NautilusIconContainer *container); +static void activate_selected_items (NautilusIconContainer *container, + int select_location); static void nautilus_icon_container_initialize_class (NautilusIconContainerClass *class); static void nautilus_icon_container_initialize (NautilusIconContainer *container); static void nautilus_icon_container_theme_changed (gpointer user_data); +static void nautilus_icon_container_annotation_changed (gpointer user_data); static void compute_stretch (StretchState *start, StretchState *current); @@ -166,10 +168,12 @@ enum { CONTEXT_CLICK_SELECTION, MIDDLE_CLICK, GET_CONTAINER_URI, + GET_ICON_CONTROL, GET_ICON_IMAGES, GET_ICON_TEXT, GET_ICON_URI, GET_ICON_DROP_TARGET_URI, + GET_ICON_ANNOTATION, GET_STORED_ICON_POSITION, ICON_POSITION_CHANGED, ICON_TEXT_CHANGED, @@ -2286,7 +2290,6 @@ select_previous_or_next_name (NautilusIconContainer *container, } /* GtkObject methods. */ - static void destroy (GtkObject *object) { @@ -2333,6 +2336,10 @@ destroy (GtkObject *object) gtk_object_destroy (GTK_OBJECT (container->details->rename_widget)); } + /* remove the annotation preference callbacks */ + nautilus_preferences_remove_callback (NAUTILUS_PREFERENCES_LOOKUP_ANNOTATIONS, nautilus_icon_container_annotation_changed, container); + nautilus_preferences_remove_callback (NAUTILUS_PREFERENCES_DISPLAY_ANNOTATIONS, nautilus_icon_container_annotation_changed, container); + /* FIXME: The code to extract colors from the theme should be in FMDirectoryView, not here. * The NautilusIconContainer class should simply provide calls to set the colors. */ @@ -2530,13 +2537,42 @@ button_press_event (GtkWidget *widget, return return_value; } +/* utility routine to determine which portion of an icon was clicked, and return the + * emblem index if an emblem was clicked on + */ +static int +hit_test_item (NautilusIconCanvasItem *icon_item, GdkEventButton *event) +{ + ArtDRect world_rect; + ArtIRect canvas_rect; + HitType hit_type; + int emblem_index; + + gnome_canvas_window_to_world (GNOME_CANVAS_ITEM (icon_item)->canvas, event->x, event->y, + &world_rect.x0, &world_rect.y0); + world_rect.x1 = world_rect.x0 + 1.0; + world_rect.y1 = world_rect.y0 + 1.0; + + eel_gnome_canvas_world_to_canvas_rectangle + (GNOME_CANVAS_ITEM (icon_item)->canvas, &world_rect, &canvas_rect); + + if (nautilus_icon_canvas_item_hit_test_full (icon_item, &canvas_rect, &hit_type, &emblem_index)) { + if (hit_type == EMBLEM_HIT) { + return emblem_index; + } + } + return 0; +} + static void nautilus_icon_container_did_not_drag (NautilusIconContainer *container, GdkEventButton *event) { + int click_location; NautilusIconContainerDetails *details; + details = container->details; - + if (!button_event_modifies_selection (event) && !details->drag_icon->is_selected) { gboolean selection_changed; @@ -2565,7 +2601,8 @@ nautilus_icon_container_did_not_drag (NautilusIconContainer *container, * NautilusList goes the other way because its "links" seem * much more link-like. */ - activate_selected_items (container); + click_location = hit_test_item (details->drag_icon->item, event); + activate_selected_items (container, click_location); } } } @@ -2997,7 +3034,7 @@ key_press_event (GtkWidget *widget, break; case GDK_Return: case GDK_KP_Enter: - activate_selected_items (container); + activate_selected_items (container, 0); handled = TRUE; break; case GDK_Escape: @@ -3070,9 +3107,10 @@ nautilus_icon_container_initialize_class (NautilusIconContainerClass *class) object_class->type, GTK_SIGNAL_OFFSET (NautilusIconContainerClass, activate), - gtk_marshal_NONE__POINTER, - GTK_TYPE_NONE, 1, - GTK_TYPE_POINTER); + gtk_marshal_NONE__POINTER_INT, + GTK_TYPE_NONE, 2, + GTK_TYPE_POINTER, + GTK_TYPE_INT); signals[CONTEXT_CLICK_SELECTION] = gtk_signal_new ("context_click_selection", GTK_RUN_LAST, @@ -3147,6 +3185,16 @@ nautilus_icon_container_initialize_class (NautilusIconContainerClass *class) gtk_marshal_NONE__POINTER, GTK_TYPE_NONE, 1, GTK_TYPE_POINTER); + signals[GET_ICON_CONTROL] + = gtk_signal_new ("get_icon_control", + GTK_RUN_LAST, + object_class->type, + GTK_SIGNAL_OFFSET (NautilusIconContainerClass, + get_icon_control), + gtk_marshal_NONE__POINTER_POINTER, + GTK_TYPE_NONE, 2, + GTK_TYPE_POINTER, + GTK_TYPE_POINTER); signals[GET_ICON_IMAGES] = gtk_signal_new ("get_icon_images", GTK_RUN_LAST, @@ -3187,6 +3235,16 @@ nautilus_icon_container_initialize_class (NautilusIconContainerClass *class) eel_gtk_marshal_STRING__POINTER, GTK_TYPE_STRING, 1, GTK_TYPE_POINTER); + signals[GET_ICON_ANNOTATION] + = gtk_signal_new ("get_icon_annotation", + GTK_RUN_LAST, + object_class->type, + GTK_SIGNAL_OFFSET (NautilusIconContainerClass, + get_icon_annotation), + eel_gtk_marshal_STRING__POINTER_INT, + GTK_TYPE_STRING, 2, + GTK_TYPE_POINTER, + GTK_TYPE_INT); signals[COMPARE_ICONS] = gtk_signal_new ("compare_icons", GTK_RUN_LAST, @@ -3384,6 +3442,10 @@ nautilus_icon_container_initialize (NautilusIconContainer *container) gtk_signal_connect (GTK_OBJECT (container), "focus-out-event", handle_focus_out_event, NULL); + /* add callbacks to notify us when the annotation state changes */ + nautilus_preferences_add_callback (NAUTILUS_PREFERENCES_LOOKUP_ANNOTATIONS, nautilus_icon_container_annotation_changed, container); + nautilus_preferences_add_callback (NAUTILUS_PREFERENCES_DISPLAY_ANNOTATIONS, nautilus_icon_container_annotation_changed, container); + /* FIXME: The code to extract colors from the theme should be in FMDirectoryView, not here. * The NautilusIconContainer class should simply provide calls to set the colors. */ @@ -3519,7 +3581,7 @@ handle_icon_button_press (NautilusIconContainer *container, details->drag_button = 0; details->drag_icon = NULL; - activate_selected_items (container); + activate_selected_items (container, 0); } return TRUE; @@ -3674,7 +3736,7 @@ icon_destroy (NautilusIconContainer *container, /* activate any selected items in the container */ static void -activate_selected_items (NautilusIconContainer *container) +activate_selected_items (NautilusIconContainer *container, int select_location) { GList *selection; @@ -3684,7 +3746,8 @@ activate_selected_items (NautilusIconContainer *container) if (selection != NULL) { gtk_signal_emit (GTK_OBJECT (container), signals[ACTIVATE], - selection); + selection, + select_location); } g_list_free (selection); } @@ -3720,6 +3783,7 @@ nautilus_icon_container_update_icon (NautilusIconContainer *container, GdkPixbuf *pixbuf, *emblem_pixbuf, *saved_pixbuf; GList *emblem_scalable_icons, *emblem_pixbufs, *p; char *editable_text, *additional_text; + GtkWidget *embedded_control; GdkFont *font; int smooth_font_size; @@ -3754,8 +3818,7 @@ nautilus_icon_container_update_icon (NautilusIconContainer *container, eel_scalable_icon_unref (scalable_icon); - /* in the rare case an image is too small, scale it up */ - + /* in the rare case an image is too small, scale it up */ width = gdk_pixbuf_get_width (pixbuf); height = gdk_pixbuf_get_height (pixbuf); if (width < min_image_size || height < min_image_size) { @@ -3763,9 +3826,11 @@ nautilus_icon_container_update_icon (NautilusIconContainer *container, /* don't let it exceed the maximum width in the other dimension */ scale_factor = MIN (scale_factor, max_image_size / width); scale_factor = MIN (scale_factor, max_image_size / height); - + scaled_width = floor (width * scale_factor + .5); scaled_height = floor (height * scale_factor + .5); + + /* scale the image to the calculated size */ saved_pixbuf = pixbuf; pixbuf = gdk_pixbuf_scale_simple (pixbuf, scaled_width, scaled_height, GDK_INTERP_BILINEAR); gdk_pixbuf_unref (saved_pixbuf); @@ -3794,13 +3859,22 @@ nautilus_icon_container_update_icon (NautilusIconContainer *container, emblem_pixbufs = g_list_reverse (emblem_pixbufs); eel_scalable_icon_list_free (emblem_scalable_icons); + /* get the embedded control, if any */ + embedded_control = nautilus_icon_canvas_item_get_control (icon->item); + if (embedded_control == NULL) { + gtk_signal_emit (GTK_OBJECT (container), + signals[GET_ICON_CONTROL], + icon->data, + &embedded_control); + } + /* Get both editable and non-editable icon text */ gtk_signal_emit (GTK_OBJECT (container), signals[GET_ICON_TEXT], icon->data, &editable_text, &additional_text); - + /* If name of icon being renamed was changed from elsewhere, end renaming mode. * Alternatively, we could replace the characters in the editable text widget * with the new name, but that could cause timing problems if the user just @@ -3825,6 +3899,8 @@ nautilus_icon_container_update_icon (NautilusIconContainer *container, "smooth_font", details->smooth_label_font, NULL); + nautilus_icon_canvas_item_set_control (icon->item, embedded_control); + nautilus_icon_canvas_item_set_image (icon->item, pixbuf); nautilus_icon_canvas_item_set_attach_points (icon->item, &attach_points); nautilus_icon_canvas_item_set_emblems (icon->item, emblem_pixbufs); @@ -5073,6 +5149,33 @@ update_label_color (EelBackground *background, } } +/* handle the annotation preference changes by updating the icons */ +static void +nautilus_icon_container_annotation_changed (gpointer user_data) +{ + nautilus_icon_container_request_update_all (NAUTILUS_ICON_CONTAINER (user_data)); +} + + +/* handle fetching annotation info from the controller */ +char * +nautilus_icon_container_get_note_text (NautilusIconContainer *container, + gpointer user_data, + int emblem_index) +{ + NautilusIcon *icon; + char *note_text; + note_text = NULL; + + icon = (NautilusIcon*) user_data; + gtk_signal_emit (GTK_OBJECT (container), + signals[GET_ICON_ANNOTATION], + icon->data, + emblem_index, + ¬e_text); + + return note_text; +} /* Return if the icon container is a fixed size */ gboolean diff --git a/libnautilus-private/nautilus-icon-container.h b/libnautilus-private/nautilus-icon-container.h index 30382a19e..4510110ec 100644 --- a/libnautilus-private/nautilus-icon-container.h +++ b/libnautilus-private/nautilus-icon-container.h @@ -107,6 +107,10 @@ typedef struct { gboolean (* get_stored_icon_position) (NautilusIconContainer *container, NautilusIconData *data, NautilusIconPosition *position); + void + (* get_icon_control) (NautilusIconContainer *container, + NautilusIconData *data, + GtkWidget **control); NautilusScalableIcon * (* get_icon_images) (NautilusIconContainer *container, NautilusIconData *data, @@ -120,6 +124,9 @@ typedef struct { NautilusIconData *data); char * (* get_icon_drop_target_uri) (NautilusIconContainer *container, NautilusIconData *data); + char * (* get_icon_annotation) (NautilusIconContainer *container, + NautilusIconData *data, + int annotation_index); int (* compare_icons) (NautilusIconContainer *container, NautilusIconData *icon_a, NautilusIconData *icon_b); diff --git a/libnautilus-private/nautilus-icon-dnd.c b/libnautilus-private/nautilus-icon-dnd.c index 4d5364ea3..059051002 100644 --- a/libnautilus-private/nautilus-icon-dnd.c +++ b/libnautilus-private/nautilus-icon-dnd.c @@ -1279,11 +1279,12 @@ nautilus_icon_dnd_begin_drag (NautilusIconContainer *container, /* create a pixmap and mask to drag with */ - pixbuf = nautilus_icon_canvas_item_get_image (container->details->drag_icon->item); - + /* we want to drag semi-transparent pixbufs, but X is too slow dealing with stippled masks, so we had to remove the code; this comment is left as a memorial to it, with the hope that we get it back someday as X Windows improves */ + + pixbuf = nautilus_icon_canvas_item_get_image (container->details->drag_icon->item); /* compute the image's offset */ nautilus_icon_canvas_item_get_icon_rectangle diff --git a/libnautilus-private/nautilus-icon-private.h b/libnautilus-private/nautilus-icon-private.h index b1b73c147..d31a13265 100644 --- a/libnautilus-private/nautilus-icon-private.h +++ b/libnautilus-private/nautilus-icon-private.h @@ -254,4 +254,8 @@ void nautilus_icon_container_update_scroll_region (NautilusIconC guint32 nautilus_icon_container_get_label_color (NautilusIconContainer *container, gboolean first_line); +char * nautilus_icon_container_get_note_text (NautilusIconContainer *container, + gpointer user_data, + int emblem_index); + #endif /* NAUTILUS_ICON_CONTAINER_PRIVATE_H */ diff --git a/libnautilus-private/nautilus-link.c b/libnautilus-private/nautilus-link.c index 8fd7fcaba..76860d397 100644 --- a/libnautilus-private/nautilus-link.c +++ b/libnautilus-private/nautilus-link.c @@ -312,6 +312,35 @@ nautilus_link_local_get_additional_text (const char *path) (path, NAUTILUS_METADATA_KEY_EXTRA_TEXT); } +void nautilus_link_local_get_component_info (const char *path, + char **control_moniker, char **control_data) +{ + xmlDoc *document; + const char *mime_type; + + *control_moniker = NULL; + *control_data = NULL; + + /* Check mime type. Exit if it is not a nautilus link */ + mime_type = gnome_vfs_get_file_mime_type (path, NULL, FALSE); + if (strcmp (mime_type, "application/x-nautilus-link") != 0) { + return; + } + + document = xmlParseFile (path); + if (document != NULL) { + *control_moniker = xml_get_root_property (document, + NAUTILUS_METADATA_KEY_CONTROL_MONIKER); + + *control_data = xml_get_root_property (document, + NAUTILUS_METADATA_KEY_CONTROL_DATA); + + xmlFreeDoc (document); + } +} + + + /* utility to return the local pathname of a cached icon, given the leaf name */ /* if the icons directory hasn't been created yet, create it */ static char * diff --git a/libnautilus-private/nautilus-link.h b/libnautilus-private/nautilus-link.h index 88882ccb5..b9596eb7d 100644 --- a/libnautilus-private/nautilus-link.h +++ b/libnautilus-private/nautilus-link.h @@ -76,14 +76,21 @@ gboolean nautilus_link_local_set_link_uri (const char * none. Despite the fact that it takes a URI parameter, works only if * the file is local and does sync. I/O. */ -char * nautilus_link_local_get_additional_text (const char *path); +char * nautilus_link_local_get_additional_text (const char *path); /* Returns the image associated with a link file. Despite the fact * that it takes a URI parameter, works only if the file is local and * does sync. I/O on the link, although it does async. on the image * and caches if the image is remote. */ -char * nautilus_link_local_get_image_uri (const char *path); +char * nautilus_link_local_get_image_uri (const char *path); + +/* returns the moniker of the component associated with a link file, as well as configuration data. + * It works only if the file is local and does sync. I/O. + */ +void nautilus_link_local_get_component_info (const char *path, + char **control_moniker, + char **control_data); /* Returns the link type of a link file. * Works only if the file is local and does sync. I/O diff --git a/libnautilus-private/nautilus-metadata.h b/libnautilus-private/nautilus-metadata.h index 00f96a2da..70681853f 100644 --- a/libnautilus-private/nautilus-metadata.h +++ b/libnautilus-private/nautilus-metadata.h @@ -72,6 +72,12 @@ #define NAUTILUS_METADATA_KEY_ICON_SCALE "icon_scale" #define NAUTILUS_METADATA_KEY_CUSTOM_ICON "custom_icon" +#define NAUTILUS_METADATA_KEY_FILE_DIGEST "digest" +#define NAUTILUS_METADATA_KEY_NOTES_INFO "notes_info" + +#define NAUTILUS_METADATA_KEY_CONTROL_MONIKER "control_moniker" +#define NAUTILUS_METADATA_KEY_CONTROL_DATA "control_data" + /* per link file */ #define NAUTILUS_METADATA_KEY_EXTRA_TEXT "extra_text" diff --git a/nautilus-clean.sh b/nautilus-clean.sh index bbdc458b7..d6b86c357 100755 --- a/nautilus-clean.sh +++ b/nautilus-clean.sh @@ -96,6 +96,7 @@ nautilus-summary-view \ nautilus-text-view \ nautilus-throbber \ nautilus-tree-view \ +nautilus-vcard \ trilobite-eazel-install-service \ trilobite-eazel-time-view \ " diff --git a/src/file-manager/Makefile.am b/src/file-manager/Makefile.am index a1c825f98..0366f104f 100644 --- a/src/file-manager/Makefile.am +++ b/src/file-manager/Makefile.am @@ -19,6 +19,7 @@ INCLUDES = \ libnautilus_file_manager_la_SOURCES= \ + fm-annotation-window.c \ fm-desktop-icon-view.c \ fm-directory-view.c \ fm-error-reporting.c \ @@ -31,6 +32,7 @@ libnautilus_file_manager_la_SOURCES= \ $(NULL) noinst_HEADERS = \ + fm-annotation-window.h \ fm-desktop-icon-view.h \ fm-directory-view.h \ fm-error-reporting.h \ diff --git a/src/file-manager/fm-annotation-window.c b/src/file-manager/fm-annotation-window.c new file mode 100644 index 000000000..022cbb0c4 --- /dev/null +++ b/src/file-manager/fm-annotation-window.c @@ -0,0 +1,400 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-annotation-window.c - window that lets user modify file annotations + + Copyright (C) 2001 Eazel, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: Andy Hertzfeld <andy@eazel.com> +*/ + +#include <config.h> +#include "fm-annotation-window.h" + +#include "fm-error-reporting.h" +#include <gtk/gtkfilesel.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkhseparator.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkmain.h> +#include <gtk/gtknotebook.h> +#include <gtk/gtkoptionmenu.h> +#include <gtk/gtkpixmap.h> +#include <gtk/gtkscrolledwindow.h> +#include <gtk/gtksignal.h> +#include <gtk/gtktable.h> +#include <gtk/gtktext.h> +#include <gtk/gtkvbox.h> +#include <libgnome/gnome-defs.h> +#include <libgnome/gnome-i18n.h> +#include <libgnomeui/gnome-dialog.h> +#include <libgnomeui/gnome-uidefs.h> +#include <libgnomevfs/gnome-vfs.h> +#include <libnautilus-extensions/nautilus-annotation.h> +#include <libnautilus-extensions/nautilus-customization-data.h> +#include <eel/eel-ellipsizing-label.h> +#include <libnautilus-extensions/nautilus-entry.h> +#include <libnautilus-extensions/nautilus-file-attributes.h> +#include <libnautilus-extensions/nautilus-file-utilities.h> +#include <libnautilus-extensions/nautilus-font-factory.h> +#include <eel/eel-gdk-pixbuf-extensions.h> +#include <eel/eel-glib-extensions.h> +#include <libnautilus-extensions/nautilus-global-preferences.h> +#include <eel/eel-gnome-extensions.h> +#include <eel/eel-gtk-extensions.h> +#include <eel/eel-gtk-macros.h> +#include <libnautilus-extensions/nautilus-icon-factory.h> +#include <eel/eel-image.h> +#include <libnautilus-extensions/nautilus-link.h> +#include <libnautilus-extensions/nautilus-metadata.h> +#include <eel/eel-stock-dialogs.h> +#include <eel/eel-string.h> +#include <libnautilus-extensions/nautilus-undo-signal-handlers.h> +#include <libnautilus/nautilus-undo.h> +#include <eel/eel-wrap-table.h> +#include <eel/eel-labeled-image.h> +#include <eel/eel-viewport.h> +#include <string.h> + +struct FMAnnotationWindowDetails { + NautilusFile *file; + GtkWidget *file_icon; + GtkLabel *file_title; + GtkWidget *text_field; + char *access_mode; +}; + + +static void real_destroy (GtkObject *object); +static void fm_annotation_window_initialize_class (FMAnnotationWindowClass *class); +static void fm_annotation_window_initialize (FMAnnotationWindow *window); +static FMAnnotationWindow* create_annotation_window + (NautilusFile *file, + FMDirectoryView *directroy_view); + +EEL_DEFINE_CLASS_BOILERPLATE (FMAnnotationWindow, fm_annotation_window, GNOME_TYPE_DIALOG) + +static void +fm_annotation_window_initialize_class (FMAnnotationWindowClass *class) +{ + GtkObjectClass *object_class; + + object_class = GTK_OBJECT_CLASS (class); + object_class->destroy = real_destroy; +} + +static void +fm_annotation_window_initialize (FMAnnotationWindow *window) +{ + window->details = g_new0 (FMAnnotationWindowDetails, 1); + window->details->file = NULL; + + /* default to local access only */ + window->details->access_mode = g_strdup ("local"); +} + +static void +real_destroy (GtkObject *object) +{ + FMAnnotationWindow *window; + + window = FM_ANNOTATION_WINDOW (object); + + nautilus_file_unref (window->details->file); + + g_free (window->details->access_mode); + g_free (window->details); + + EEL_CALL_PARENT (GTK_OBJECT_CLASS, destroy, (object)); +} + +static GdkPixbuf * +get_pixbuf_for_annotation_window (NautilusFile *file) +{ + g_assert (NAUTILUS_IS_FILE (file)); + + return nautilus_icon_factory_get_pixbuf_for_file (file, NULL, NAUTILUS_ICON_SIZE_LARGE, TRUE); +} + + +static void +update_annotation_window_icon (EelImage *image) +{ + GdkPixbuf *pixbuf; + NautilusFile *file; + + g_assert (EEL_IS_IMAGE (image)); + + file = gtk_object_get_data (GTK_OBJECT (image), "nautilus_file"); + + g_assert (NAUTILUS_IS_FILE (file)); + + pixbuf = get_pixbuf_for_annotation_window (file); + + eel_image_set_pixbuf (image, pixbuf); + + gdk_pixbuf_unref (pixbuf); +} + +static GtkWidget * +create_image_widget_for_file (NautilusFile *file) +{ + GtkWidget *image; + GdkPixbuf *pixbuf; + + pixbuf = get_pixbuf_for_annotation_window (file); + + image = eel_image_new (NULL); + + eel_image_set_pixbuf (EEL_IMAGE (image), pixbuf); + + gdk_pixbuf_unref (pixbuf); + + nautilus_file_ref (file); + gtk_object_set_data_full (GTK_OBJECT (image), + "nautilus_file", + file, + (GtkDestroyNotify) nautilus_file_unref); + + gtk_signal_connect_object_while_alive (nautilus_icon_factory_get (), + "icons_changed", + update_annotation_window_icon, + GTK_OBJECT (image)); + + gtk_signal_connect_object_while_alive (GTK_OBJECT (file), + "changed", + update_annotation_window_icon, + GTK_OBJECT (image)); + return image; +} + + +static void +update_annotation_window_title (GtkWindow *window, NautilusFile *file) +{ + char *name, *title; + + g_assert (NAUTILUS_IS_FILE (file)); + g_assert (GTK_IS_WINDOW (window)); + + name = nautilus_file_get_name (file); + title = g_strdup_printf (_("%s Annotation"), name); + gtk_window_set_title (window, title); + + g_free (name); + g_free (title); +} + +/* callback for access menu items */ +static void +set_access_mode (GtkWidget *menu_item, const char* access_mode) +{ + FMAnnotationWindow *window; + window = FM_ANNOTATION_WINDOW (gtk_object_get_user_data (GTK_OBJECT (menu_item))); + g_free (window->details->access_mode); + window->details->access_mode = g_strdup (access_mode); +} + +/* utility to create and append an access menu item */ +static void +add_access_menu_item (FMAnnotationWindow *window, GtkWidget *access_menu, const char *menu_label, const char *id) +{ + GtkWidget *menu_item; + + menu_item = gtk_menu_item_new_with_label (menu_label); + gtk_widget_show (menu_item); + gtk_menu_append (GTK_MENU (access_menu), menu_item); + + gtk_object_set_user_data (GTK_OBJECT (menu_item), window); + + gtk_signal_connect_full (GTK_OBJECT (menu_item), + "activate", + GTK_SIGNAL_FUNC (set_access_mode), + NULL, + g_strdup (id), + g_free, + FALSE, + FALSE); + +} + +/* create the option table for the annotation window */ +static GtkWidget * +create_options_table (FMAnnotationWindow *window) +{ + GtkWidget *table; + GtkWidget *label; + GtkWidget *type_menu_vbox; + GtkWidget *access_menu_vbox; + GtkWidget *option_menu; + GtkWidget *new_menu; + GtkWidget *menu_item; + + table = gtk_table_new (2, 2, FALSE); + + label = gtk_label_new (_("Type:")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 0, 1, GTK_FILL, GTK_FILL, 4, 4); + + /* type options will eventually be derived from an xml file, but there's only one + * for now + */ + type_menu_vbox = gtk_vbox_new (FALSE, GNOME_PAD_SMALL); + option_menu = gtk_option_menu_new (); + gtk_box_pack_end (GTK_BOX (type_menu_vbox), option_menu, TRUE, FALSE, 0); + gtk_table_attach(GTK_TABLE(table), type_menu_vbox, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 4, 4); + + new_menu = gtk_menu_new (); + menu_item = gtk_menu_item_new_with_label (_("free form note")); + gtk_widget_show (new_menu); + gtk_widget_show (menu_item); + gtk_menu_append (GTK_MENU (new_menu), menu_item); + gtk_option_menu_set_menu (GTK_OPTION_MENU (option_menu), new_menu); + + /* now make the access option menu */ + label = gtk_label_new (_("Access:")); + gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 4, 4); + + access_menu_vbox = gtk_vbox_new (FALSE, GNOME_PAD_SMALL); + option_menu = gtk_option_menu_new (); + gtk_box_pack_end (GTK_BOX (access_menu_vbox), option_menu, TRUE, FALSE, 0); + gtk_table_attach(GTK_TABLE(table), access_menu_vbox, 1, 2, 1, 2, GTK_FILL, GTK_FILL, 4, 4); + + new_menu = gtk_menu_new (); + gtk_widget_show (new_menu); + + add_access_menu_item (window, new_menu, _("local only"), "local"); + gtk_option_menu_set_menu (GTK_OPTION_MENU (option_menu), new_menu); + + add_access_menu_item (window, new_menu, _("share globally"), "global"); + return table; +} + +/* this callback is invoked when the OK or Cancel buttons are clicked, to add an annotation and dismiss the window */ +static void +annotation_clicked_callback (GtkWidget *dialog, int which_button, gpointer user_data) +{ + char *notes_text; + FMAnnotationWindow *window; + + window = FM_ANNOTATION_WINDOW (dialog); + + if (which_button == GNOME_OK) { + notes_text = gtk_editable_get_chars (GTK_EDITABLE (window->details->text_field), 0 , -1); + /* type field is hard-wired for now */ + nautilus_annotation_add_annotation (window->details->file, + "text", + notes_text, + window->details->access_mode); + g_free (notes_text); + } + gtk_widget_destroy (dialog); +} + +/* create the annotation window, and allocate its widgets */ +static FMAnnotationWindow * +create_annotation_window (NautilusFile *file, FMDirectoryView *directory_view) +{ + FMAnnotationWindow *window; + GtkWidget *hbox; + GtkBox *content_box; + GtkWidget *label; + GtkWidget *table; + char *file_name, *title; + GdkFont *font; + int position; + char *annotation_text; + + window = FM_ANNOTATION_WINDOW (gtk_widget_new (fm_annotation_window_get_type (), NULL)); + + window->details->file = nautilus_file_ref (file); + + gtk_container_set_border_width (GTK_CONTAINER (window), GNOME_PAD); + gtk_window_set_policy (GTK_WINDOW (window), FALSE, TRUE, FALSE); + gtk_window_set_wmclass (GTK_WINDOW (window), "file_annotation", "Nautilus"); + + /* add the buttons */ + gnome_dialog_append_buttons (GNOME_DIALOG (window), GNOME_STOCK_BUTTON_OK, GNOME_STOCK_BUTTON_CANCEL, NULL); + gnome_dialog_set_default( GNOME_DIALOG (window), GNOME_OK); + + gtk_signal_connect (GTK_OBJECT (window), "clicked", (GtkSignalFunc) annotation_clicked_callback, NULL); + + /* get the container box of the dialog */ + content_box = GTK_BOX (GNOME_DIALOG (window)->vbox); + + /* allocate an hbox to hold the icon and the title */ + hbox = gtk_hbox_new (FALSE, GNOME_PAD); + gtk_box_pack_start (GTK_BOX (content_box), hbox, FALSE, FALSE, GNOME_PAD); + + /* allocate an icon and title */ + window->details->file_icon = create_image_widget_for_file (window->details->file); + gtk_box_pack_start (GTK_BOX (hbox), window->details->file_icon, FALSE, FALSE, GNOME_PAD); + + file_name = nautilus_file_get_name (window->details->file); + title = g_strdup_printf (_("Add Annotation to %s"), file_name); + label = gtk_label_new (title); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, GNOME_PAD); + g_free (file_name); + g_free (title); + + /* now allocate a table to hold and populate the option boxes */ + table = create_options_table (window); + gtk_box_pack_start (GTK_BOX (content_box), table, FALSE, FALSE, GNOME_PAD); + + /* add the views as defined by the currently selected type */ + /* at first, there is only one hardwired type, so just add it here */ + window->details->text_field = gtk_text_new (NULL, NULL); + + font = nautilus_font_factory_get_font_from_preferences (14); + eel_gtk_widget_set_font (window->details->text_field, font); + gdk_font_unref (font); + + gtk_text_set_editable (GTK_TEXT (window->details->text_field), TRUE); + gtk_box_pack_start (GTK_BOX (content_box), window->details->text_field, TRUE, TRUE, 0); + + /* set up the annotation field with the initial text, if any */ + annotation_text = nautilus_annotation_get_annotation_for_display (window->details->file); + position = 0; + if (annotation_text) { + gtk_editable_insert_text (GTK_EDITABLE (window->details->text_field), + annotation_text, + strlen (annotation_text), + &position); + + g_free (annotation_text); + } + + update_annotation_window_title (GTK_WINDOW (window), window->details->file); + + gtk_widget_show_all (GTK_WIDGET (window)); + return window; +} + +void +fm_annotation_window_present (NautilusFile *file, FMDirectoryView *directory_view) +{ + g_return_if_fail (NAUTILUS_IS_FILE (file)); + g_return_if_fail (FM_IS_DIRECTORY_VIEW (directory_view)); + + if (nautilus_file_is_directory (file)) { + eel_show_error_dialog (_("Sorry, but you currently can't add an annotation to a directory."), _("Can't annotate directory"), NULL); + return; + } + + create_annotation_window (file, directory_view); +} + diff --git a/src/file-manager/fm-annotation-window.h b/src/file-manager/fm-annotation-window.h new file mode 100644 index 000000000..6b96195bc --- /dev/null +++ b/src/file-manager/fm-annotation-window.h @@ -0,0 +1,65 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* fm-annotation-window.h - interface for window that lets user add and edit + file annotations. + + Copyright (C) 2001 Eazel, Inc. + + The Gnome Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. + + Authors: Andy Hertzfeld <andy@eazel.com> +*/ + +#ifndef FM_ANNOTATION_WINDOW_H +#define FM_ANNOTATION_WINDOW_H + +#include "fm-directory-view.h" + +#include <gtk/gtkwindow.h> +#include <libgnomeui/gnome-dialog.h> +#include <libnautilus-extensions/nautilus-file.h> + +typedef struct FMAnnotationWindow FMAnnotationWindow; + +#define FM_TYPE_ANNOTATION_WINDOW \ + (fm_annotation_window_get_type ()) +#define FM_ANNOTATION_WINDOW(obj) \ + (GTK_CHECK_CAST ((obj), FM_TYPE_ANNOTATION_WINDOW, FMAnnotationWindow)) +#define FM_ANNOTATION_WINDOW_CLASS(klass) \ + (GTK_CHECK_CLASS_CAST ((klass), FM_TYPE_ANNOTATION_WINDOW, FMAnnotationWindowClass)) +#define FM_IS_ANNOTATION_WINDOW(obj) \ + (GTK_CHECK_TYPE ((obj), FM_TYPE_ANNOTATION_WINDOW)) +#define FM_IS_ANNOTATION_WINDOW_CLASS(klass) \ + (GTK_CHECK_CLASS_TYPE ((klass), FM_TYPE_ANNOTATION_WINDOW)) + +typedef struct FMAnnotationWindowDetails FMAnnotationWindowDetails; + +struct FMAnnotationWindow { + GnomeDialog dialog; + FMAnnotationWindowDetails *details; +}; + +struct FMAnnotationWindowClass { + GnomeDialogClass parent_class; +}; + +typedef struct FMAnnotationWindowClass FMAnnotationWindowClass; + +GtkType fm_annotation_window_get_type (void); +void fm_annotation_window_present (NautilusFile *file, + FMDirectoryView *directory_view); + +#endif /* FM_ANNOTATION_WINDOW_H */ diff --git a/src/file-manager/fm-icon-view.c b/src/file-manager/fm-icon-view.c index 96c8bf578..004225d32 100644 --- a/src/file-manager/fm-icon-view.c +++ b/src/file-manager/fm-icon-view.c @@ -25,9 +25,11 @@ #include <config.h> #include "fm-icon-view.h" +#include "fm-annotation-window.h" #include "fm-desktop-icon-view.h" #include "fm-error-reporting.h" #include "fm-icon-text-window.h" +#include <bonobo/bonobo-widget.h> #include <bonobo/bonobo-ui-util.h> #include <ctype.h> #include <errno.h> @@ -45,6 +47,7 @@ #include <libgnomevfs/gnome-vfs-xfer.h> #include <libnautilus-extensions/nautilus-audio-player.h> #include <eel/eel-background.h> +#include <libnautilus-extensions/nautilus-annotation.h> #include <libnautilus-extensions/nautilus-bonobo-extensions.h> #include <libnautilus-extensions/nautilus-directory-background.h> #include <libnautilus-extensions/nautilus-directory.h> @@ -73,6 +76,7 @@ /* Paths to use when creating & referring to Bonobo menu items */ #define MENU_PATH_RENAME "/menu/File/File Items Placeholder/Rename" +#define MENU_PATH_ANNOTATE "/menu/File/File Items Placeholder/Annotate" #define MENU_PATH_CUSTOMIZE_ICON_TEXT "/menu/Edit/Global Edit Items Placeholder/Icon Text" #define MENU_PATH_STRETCH_ICON "/menu/Edit/Edit Items Placeholder/Stretch" #define MENU_PATH_UNSTRETCH_ICONS "/menu/Edit/Edit Items Placeholder/Unstretch" @@ -86,6 +90,7 @@ #define COMMAND_PREFIX "/commands/" #define COMMAND_RENAME "/commands/Rename" +#define COMMAND_ANNOTATE "/commands/Annotate" #define COMMAND_STRETCH_ICON "/commands/Stretch" #define COMMAND_UNSTRETCH_ICONS "/commands/Unstretch" #define COMMAND_TIGHTER_LAYOUT "/commands/Tighter Layout" @@ -401,6 +406,23 @@ rename_icon_callback (BonoboUIComponent *component, gpointer callback_data, cons } static void +annotate_callback (BonoboUIComponent *component, gpointer callback_data, const char *verb) +{ + GList *selected_files; + NautilusFile *first_file; + + g_assert (FM_IS_ICON_VIEW (callback_data)); + + /* show the annotation window */ + selected_files = fm_directory_view_get_selection (FM_DIRECTORY_VIEW (callback_data)); + if (selected_files != NULL) { + first_file = NAUTILUS_FILE (selected_files->data); + fm_annotation_window_present (first_file, FM_DIRECTORY_VIEW (callback_data)); + g_list_free (selected_files); + } +} + +static void set_tighter_layout (FMIconView *icon_view, gboolean new_value) { fm_icon_view_set_directory_tighter_layout (icon_view, @@ -1251,6 +1273,7 @@ fm_icon_view_merge_menus (FMDirectoryView *view) FMIconView *icon_view; BonoboUIVerb verbs [] = { BONOBO_UI_VERB ("Rename", rename_icon_callback), + BONOBO_UI_VERB ("Annotate", annotate_callback), BONOBO_UI_VERB ("Icon Text", customize_icon_text_callback), BONOBO_UI_VERB ("Stretch", show_stretch_handles_callback), BONOBO_UI_VERB ("Unstretch", unstretch_icons_callback), @@ -1340,6 +1363,10 @@ fm_icon_view_update_menus (FMDirectoryView *view) COMMAND_RENAME, selection_count == 1 && nautilus_file_can_rename (selection->data)); + + nautilus_bonobo_set_sensitive (icon_view->details->ui, + COMMAND_ANNOTATE, + selection_count == 1); bonobo_ui_component_thaw (icon_view->details->ui, NULL); @@ -1420,14 +1447,57 @@ fm_icon_view_set_selection (FMDirectoryView *view, GList *selection) (get_icon_container (FM_ICON_VIEW (view)), selection); } +/* utility routine to return the specified keyword, given a file and emblem index */ +static char * +get_keyword_by_index (NautilusFile *file, int emblem_index) +{ + GList *keyword_list, *selected_keyword; + char *keyword; + + keyword_list = nautilus_file_get_emblem_names (file); + if (keyword_list == NULL) { + return NULL; + } + + selected_keyword = g_list_nth (keyword_list, emblem_index - 1); + if (selected_keyword == NULL) { + eel_g_list_free_deep (keyword_list); + return NULL; + } + + keyword = g_strdup (selected_keyword->data); + eel_g_list_free_deep (keyword_list); + + return keyword; +} + static void icon_container_activate_callback (NautilusIconContainer *container, GList *file_list, + int emblem_index, FMIconView *icon_view) { + char *keyword; + NautilusFile *file; + g_assert (FM_IS_ICON_VIEW (icon_view)); g_assert (container == get_icon_container (icon_view)); + /* if there is a single file to be activated, and the user clicked on an emblem, + * implement the emblem-specific action instead of activation. For now, + * we just handle annotations. + */ + if (emblem_index > 0 && file_list != NULL && file_list->next == NULL) { + file = NAUTILUS_FILE (file_list->data); + keyword = get_keyword_by_index (file, emblem_index); + if (eel_strcmp (keyword, "note") == 0) { + fm_annotation_window_present (file, FM_DIRECTORY_VIEW (icon_view)); + g_free (keyword); + return; + } + g_free (keyword); + } + fm_directory_view_activate_files (FM_DIRECTORY_VIEW (icon_view), file_list); } @@ -1833,6 +1903,38 @@ get_icon_images_callback (NautilusIconContainer *container, return nautilus_icon_factory_get_icon_for_file (file, modifier, smooth_graphics); } +/* return the Bonobo control associated with the icon, if any */ +static void +get_icon_control_callback (NautilusIconContainer *container, + NautilusFile *file, + GtkWidget **control, + FMIconView *icon_view) +{ + Bonobo_UIContainer ui_container; + char *control_moniker, *control_data; + char *uri, *path; + *control = NULL; + + if (nautilus_file_is_nautilus_link (file)) { + uri = nautilus_file_get_uri (file); + path = gnome_vfs_get_local_path_from_uri (uri); + if (path != NULL) { + nautilus_link_local_get_component_info (path, &control_moniker, &control_data); + if (control_moniker && strlen (control_moniker) > 0) { + ui_container = fm_directory_view_get_bonobo_ui_container (FM_DIRECTORY_VIEW (icon_view)); + *control = bonobo_widget_new_control (control_moniker, ui_container); + g_free (control_moniker); + } + if (control_data && strlen (control_data) > 0) { + bonobo_widget_set_property (BONOBO_WIDGET (*control), "configuration", control_data, NULL); + g_free (control_data); + } + g_free (path); + } + g_free (uri); + } +} + static char * get_icon_uri_callback (NautilusIconContainer *container, NautilusFile *file, @@ -1897,6 +1999,7 @@ get_icon_text_callback (NautilusIconContainer *container, g_assert (additional_text != NULL); g_assert (FM_IS_ICON_VIEW (icon_view)); + /* In the smallest zoom mode, no text is drawn. */ if (fm_icon_view_get_zoom_level (icon_view) == NAUTILUS_ZOOM_LEVEL_SMALLEST) { *editable_text = NULL; @@ -1904,7 +2007,7 @@ get_icon_text_callback (NautilusIconContainer *container, /* Strip the suffix for nautilus object xml files. */ *editable_text = nautilus_file_get_name (file); } - + /* Handle link files specially. */ if (nautilus_file_is_nautilus_link (file)) { /* FIXME bugzilla.eazel.com 2531: Does sync. I/O and works only locally. */ @@ -1955,6 +2058,26 @@ get_icon_text_callback (NautilusIconContainer *container, g_strfreev (text_array); } +/* this callback returns the annotation text associated with the passed-in file and emblem index */ +static char * +get_icon_annotation_callback (NautilusIconContainer *container, + NautilusFile *file, + int emblem_index, + FMIconView *icon_view) +{ + char *keyword; + + keyword = get_keyword_by_index (file, emblem_index); + + /* if the keyword is "note", return the file annotation instead */ + if (eel_strcmp (keyword, "note") == 0) { + g_free (keyword); + keyword = nautilus_annotation_get_annotation (file); + } + + return keyword; +} + /* Preferences changed callbacks */ static void fm_icon_view_text_attribute_names_changed (FMDirectoryView *directory_view) @@ -2446,6 +2569,10 @@ create_icon_container (FMIconView *icon_view) GTK_SIGNAL_FUNC (get_icon_images_callback), icon_view); gtk_signal_connect (GTK_OBJECT (icon_container), + "get_icon_control", + GTK_SIGNAL_FUNC (get_icon_control_callback), + icon_view); + gtk_signal_connect (GTK_OBJECT (icon_container), "get_icon_uri", GTK_SIGNAL_FUNC (get_icon_uri_callback), icon_view); @@ -2458,6 +2585,10 @@ create_icon_container (FMIconView *icon_view) GTK_SIGNAL_FUNC (get_icon_text_callback), icon_view); gtk_signal_connect (GTK_OBJECT (icon_container), + "get_icon_annotation", + GTK_SIGNAL_FUNC (get_icon_annotation_callback), + icon_view); + gtk_signal_connect (GTK_OBJECT (icon_container), "move_copy_items", GTK_SIGNAL_FUNC (icon_view_move_copy_items), directory_view); diff --git a/src/file-manager/nautilus-icon-view-ui.xml b/src/file-manager/nautilus-icon-view-ui.xml index 4cbe8b0ba..26ac06eb9 100644 --- a/src/file-manager/nautilus-icon-view-ui.xml +++ b/src/file-manager/nautilus-icon-view-ui.xml @@ -3,6 +3,9 @@ <cmd name="Rename" _label="Rename" _tip="Rename selected icon"/> + <cmd name="Annotate" + _label="Add Annotation..." + _tip="Add an annotation to this file"/> <cmd name="Icon Text" _label="Icon Captions..." _tip="Choose which information appears beneath each icon's name"/> @@ -153,6 +156,7 @@ <popup name="selection"> <placeholder name="File Actions"> <menuitem name="Rename" verb="Rename"/> + <menuitem name="Annotate" verb="Annotate"/> </placeholder> <placeholder name="Icon Appearance Items"> <menuitem name="Stretch" verb="Stretch"/> diff --git a/src/nautilus-property-browser.c b/src/nautilus-property-browser.c index f4c649c05..2fe2243e3 100644 --- a/src/nautilus-property-browser.c +++ b/src/nautilus-property-browser.c @@ -198,6 +198,8 @@ static void element_clicked_callback (GtkWidget #define MAX_ICON_WIDTH 63 #define MAX_ICON_HEIGHT 63 #define COLOR_SQUARE_SIZE 48 +#define MAX_EMBLEM_HEIGHT 52 +#define STANDARD_BUTTON_IMAGE_HEIGHT 42 #define LABELED_IMAGE_SPACING 2 #define IMAGE_TABLE_X_SPACING 6 @@ -1951,6 +1953,7 @@ nautilus_property_browser_update_contents (NautilusPropertyBrowser *property_bro IMAGE_TABLE_X_SPACING); eel_wrap_table_set_y_spacing (EEL_WRAP_TABLE (property_browser->details->content_table), IMAGE_TABLE_Y_SPACING); + gtk_container_set_border_width (GTK_CONTAINER (property_browser->details->content_table), 8); gtk_signal_connect (GTK_OBJECT (property_browser->details->content_table), "child_pressed", |