diff -Nur thunar.git.1/icons/16x16/Makefile.am thunar.git.addfunc/icons/16x16/Makefile.am --- thunar.git.1/icons/16x16/Makefile.am 2009-09-16 11:48:09.000000000 +0800 +++ thunar.git.addfunc/icons/16x16/Makefile.am 2009-10-13 10:46:31.000000000 +0800 @@ -6,6 +6,7 @@ stockdir = $(datadir)/icons/hicolor/16x16/stock/navigation stock_DATA = \ + stock_thunar-eject.png \ stock_thunar-shortcuts.png \ stock_thunar-templates.png Binary files thunar.git.1/icons/16x16/stock_thunar-eject.png and thunar.git.addfunc/icons/16x16/stock_thunar-eject.png differ diff -Nur thunar.git.1/po/zh_CN.po thunar.git.addfunc/po/zh_CN.po --- thunar.git.1/po/zh_CN.po 2009-09-16 11:48:09.000000000 +0800 +++ thunar.git.addfunc/po/zh_CN.po 2009-10-13 10:53:21.000000000 +0800 @@ -1848,6 +1848,10 @@ msgid_plural "Add the selected folders to the shortcuts side pane" msgstr[0] "加入所选文件夹到侧栏" +#: ../thunar/thunar-shortcuts-view.c:252 +msgid "Safely remove" +msgstr "安全删除" + #. append the "Mount Volume" menu action #: ../thunar/thunar-shortcuts-view.c:810 ../thunar/thunar-tree-view.c:1080 msgid "_Mount Volume" @@ -3261,6 +3265,21 @@ msgid "Open the specified folders in Thunar" msgstr "在新的 Thunar 窗口中打开指定的文件夹" +#: ../thunar/cell-renderer-diskinfo.c:667 +#, c-format +msgid "%.1f %s free of %.1f %s" +msgstr "%.1f %s 空闲 / %.1f %s" + +#: ../thunar-vfs/thunar-vfs-volume-hal.c:1185 +msgid "Unsafe device removal" +msgstr "移动设备非安全移除" + +#: ../thunar-vfs/thunar-vfs-volume-hal.c:1186 +msgid "" +"To avoid serious data loss, disable removable device with the 'Safely " +"Remove' white eject button.\n" +msgstr "为了防止数据丢失,请点击'安全删除'白色弹出按钮移除移动设备.\n" + #: ../thunar/thunar-settings.desktop.in.h:1 msgid "Configure the Thunar file manager" msgstr "配置 Thunar 文件管理器" diff -Nur thunar.git.1/thunar/cell-renderer-diskinfo.c thunar.git.addfunc/thunar/cell-renderer-diskinfo.c --- thunar.git.1/thunar/cell-renderer-diskinfo.c 1970-01-01 08:00:00.000000000 +0800 +++ thunar.git.addfunc/thunar/cell-renderer-diskinfo.c 2009-10-13 10:42:34.000000000 +0800 @@ -0,0 +1,758 @@ +#include + +#include +#include + +#include +#include +#include "cell-renderer-diskinfo.h" + +#define FIXED_WIDTH 124 +#define FIXED_HEIGHT 44 +#define FIXED_HEIGHT_TEXT 20 + + +static void cell_renderer_diskinfo_init (CellRendererDiskinfo *celldiskinfo); + +static void cell_renderer_diskinfo_class_init (CellRendererDiskinfoClass *klass); + +static void cell_renderer_diskinfo_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); + +static void cell_renderer_diskinfo_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); + +static void cell_renderer_diskinfo_finalize (GObject *gobject); + +static void cell_renderer_diskinfo_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *cell_area, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height); + +static void cell_renderer_diskinfo_render (GtkCellRenderer *cell, + GdkWindow *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + guint flags); + + +static GtkCellEditable* cell_renderer_diskinfo_start_editing (GtkCellRenderer *renderer, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags); + +static void cell_diskinfo_renderer_editing_done (GtkCellEditable *editable, + CellRendererDiskinfo *renderer); + +static void cell_diskinfo_renderer_grab_focus (GtkWidget *entry, + CellRendererDiskinfo *renderer); + +static gboolean cell_diskinfo_renderer_focus_out_event (GtkWidget *entry, + GdkEventFocus *event, + CellRendererDiskinfo *renderer); + +static gboolean cell_diskinfo_renderer_entry_menu_popdown_timer (gpointer user_data); + +static void cell_diskinfo_renderer_entry_menu_popdown_timer_destroy (gpointer user_data); + +static void cell_diskinfo_renderer_popup_unmap (GtkMenu *menu, + CellRendererDiskinfo *text_renderer); + +static void cell_diskinfo_renderer_populate_popup (GtkEntry *entry, + GtkMenu *menu, + CellRendererDiskinfo *text_renderer); + + +typedef enum +{ + TEXT_TYPE_HEADER = 0, + TEXT_TYPE_STATUS, + TEXT_TYPE_NORMAL +} TextRenderType; + + +enum +{ + PROP_USED = 1, + PROP_TOTAL, + PROP_LABEL, + PROP_EDITABLE +}; + +enum +{ + EDITED, + LAST_SIGNAL, +}; + +static gpointer parent_class; +static guint diskinfo_renderer_signals[LAST_SIGNAL]; + + +GType +cell_renderer_diskinfo_get_type (void) +{ + static GType cell_progress_type = 0; + + if (cell_progress_type == 0) + { + static const GTypeInfo cell_progress_info = + { + sizeof (CellRendererDiskinfoClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) cell_renderer_diskinfo_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (CellRendererDiskinfo), + 0, /* n_preallocs */ + (GInstanceInitFunc) cell_renderer_diskinfo_init, + }; + + /* Derive from GtkCellRenderer */ + cell_progress_type = g_type_register_static (GTK_TYPE_CELL_RENDERER, + "CellRendererDiskinfo", + &cell_progress_info, + 0); + } + + return cell_progress_type; +} + + +static void +cell_renderer_diskinfo_init (CellRendererDiskinfo *cellrendererdiskinfo) +{ + GTK_CELL_RENDERER(cellrendererdiskinfo)->mode = GTK_CELL_RENDERER_MODE_INERT; + GTK_CELL_RENDERER(cellrendererdiskinfo)->xpad = 4; + GTK_CELL_RENDERER(cellrendererdiskinfo)->ypad = 3; + + cellrendererdiskinfo->label = NULL; + cellrendererdiskinfo->name = NULL; + cellrendererdiskinfo->exp_path = NULL; + cellrendererdiskinfo->used = 0; + cellrendererdiskinfo->total = 0; + + cellrendererdiskinfo->entry_menu_popdown_timer_id = -1; +} + +static void +cell_renderer_diskinfo_class_init (CellRendererDiskinfoClass *klass) +{ + GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS(klass); + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + parent_class = g_type_class_peek_parent (klass); + object_class->finalize = cell_renderer_diskinfo_finalize; + + /* Hook up functions to set and get our + * * custom cell renderer properties */ + object_class->get_property = cell_renderer_diskinfo_get_property; + object_class->set_property = cell_renderer_diskinfo_set_property; + + /* Override the two crucial functions that are the heart + * of a cell renderer in the parent class */ + cell_class->get_size = cell_renderer_diskinfo_get_size; + cell_class->render = cell_renderer_diskinfo_render; + + cell_class->start_editing = cell_renderer_diskinfo_start_editing; + + + /* Install our very own properties */ + g_object_class_install_property (object_class, + PROP_USED, + g_param_spec_int ( + "used", + "Used space (MB)", + "The used space of the disk in MB", + 0, 1<<30, 0, G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_TOTAL, + g_param_spec_int ( + "total", + "Total space (MB)", + "The total space of the disk in MB", + 0, 1<<30, 0, G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_LABEL, + g_param_spec_string ( + "text", + "Disk Label", + "Label of the disk", + NULL, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, + PROP_EDITABLE, + g_param_spec_boolean ( + "editable", + "editable", + "editable", + FALSE, G_PARAM_READWRITE)); + + diskinfo_renderer_signals[EDITED] = + g_signal_new ("edited", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (CellRendererDiskinfoClass, edited), + NULL, NULL, + _thunar_marshal_VOID__STRING_STRING, + G_TYPE_NONE, 2, G_TYPE_STRING, G_TYPE_STRING); +} + + +/*************************************************************************** + * + * cell_renderer_diskinfo_finalize: free any resources here + * + ****************************************************************************/ + +static void +cell_renderer_diskinfo_finalize (GObject *object) +{ + CellRendererDiskinfo *diskinfo = CELL_RENDERER_DISKINFO(object); + + /* Free any dynamically allocated resources here */ + g_free (diskinfo->label); + g_free (diskinfo->name); + g_free (diskinfo->exp_path); + + (* G_OBJECT_CLASS (parent_class)->finalize) (object); +} + + +static GtkCellEditable* +cell_renderer_diskinfo_start_editing (GtkCellRenderer *renderer, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + CellRendererDiskinfo *diskinfo = CELL_RENDERER_DISKINFO (renderer); + + /* verify that we are editable */ + if (renderer->mode != GTK_CELL_RENDERER_MODE_EDITABLE) + return NULL; + + /* allocate a new text entry widget to be used for editing */ + diskinfo->entry = g_object_new (GTK_TYPE_ENTRY, + "has-frame", FALSE, + "text", diskinfo->name, + "visible", TRUE, + "xalign", renderer->xalign, + NULL); + + /* select the whole text */ + gtk_editable_select_region (GTK_EDITABLE (diskinfo->entry), 0, -1); + + /* remember the tree path that we're editing */ + g_object_set_data_full (G_OBJECT (diskinfo->entry), "cell-diskinfo-renderer-path", g_strdup (path), g_free); + + /* connect required signals */ + g_signal_connect (G_OBJECT (diskinfo->entry), + "editing-done", + G_CALLBACK (cell_diskinfo_renderer_editing_done), + diskinfo); + + g_signal_connect_after (G_OBJECT (diskinfo->entry), + "grab-focus", + G_CALLBACK (cell_diskinfo_renderer_grab_focus), + diskinfo); + + g_signal_connect (G_OBJECT (diskinfo->entry), + "focus-out-event", + G_CALLBACK (cell_diskinfo_renderer_focus_out_event), + diskinfo); + + g_signal_connect (G_OBJECT (diskinfo->entry), + "populate-popup", + G_CALLBACK (cell_diskinfo_renderer_populate_popup), + diskinfo); + + return GTK_CELL_EDITABLE (diskinfo->entry); +} + + +static void +cell_diskinfo_renderer_editing_done (GtkCellEditable *editable, + CellRendererDiskinfo *renderer) +{ + /* disconnect our signals from the cell editable */ + g_signal_handlers_disconnect_by_func (G_OBJECT (editable), cell_diskinfo_renderer_editing_done, renderer); + g_signal_handlers_disconnect_by_func (G_OBJECT (editable), cell_diskinfo_renderer_focus_out_event, renderer); + g_signal_handlers_disconnect_by_func (G_OBJECT (editable), cell_diskinfo_renderer_populate_popup, renderer); + + /* let the GtkCellRenderer class do it's part of the job */ + gtk_cell_renderer_stop_editing (GTK_CELL_RENDERER (renderer), GTK_ENTRY (editable)->editing_canceled); + + /* inform whoever is interested that we have new text (if not cancelled) */ + if (G_LIKELY (!GTK_ENTRY (editable)->editing_canceled)) + { + const gchar *path; + const gchar *text; + + text = gtk_entry_get_text (GTK_ENTRY (editable)); + path = g_object_get_data (G_OBJECT (editable), "cell-diskinfo-renderer-path"); + g_signal_emit (G_OBJECT (renderer), diskinfo_renderer_signals[EDITED], 0, path, text); + } +} + + +static gboolean +cell_diskinfo_renderer_focus_out_event (GtkWidget *entry, + GdkEventFocus *event, + CellRendererDiskinfo *renderer) +{ + /* cancel editing if we haven't popped up the menu */ + if (G_LIKELY (!renderer->entry_menu_active)) + cell_diskinfo_renderer_editing_done (GTK_CELL_EDITABLE (entry), renderer); + + /* we need to pass the event to the entry */ + return FALSE; +} + +static void +cell_diskinfo_renderer_grab_focus (GtkWidget *entry, + CellRendererDiskinfo *renderer) +{ + const gchar *text; + const gchar *dot; + glong offset; + + /* determine the text from the entry widget */ + text = gtk_entry_get_text (GTK_ENTRY (entry)); + + /* lookup the last dot in the text */ + dot = strrchr (text, '.'); + if (G_LIKELY (dot != NULL)) + { + /* determine the UTF-8 char offset */ + offset = g_utf8_pointer_to_offset (text, dot); + + /* select the text prior to the dot */ + if (G_LIKELY (offset > 0)) + gtk_entry_select_region (GTK_ENTRY (entry), 0, offset); + } + + /* disconnect the grab-focus handler, so we change the selection only once */ + g_signal_handlers_disconnect_by_func (G_OBJECT (entry), cell_diskinfo_renderer_grab_focus, renderer); +} + +static void +cell_diskinfo_renderer_populate_popup (GtkEntry *entry, + GtkMenu *menu, + CellRendererDiskinfo *renderer) +{ + if (G_UNLIKELY (renderer->entry_menu_popdown_timer_id >= 0)) + g_source_remove (renderer->entry_menu_popdown_timer_id); + + renderer->entry_menu_active = TRUE; + + g_signal_connect (G_OBJECT (menu), "unmap", G_CALLBACK (cell_diskinfo_renderer_popup_unmap), renderer); +} + +static void +cell_diskinfo_renderer_popup_unmap (GtkMenu *menu, + CellRendererDiskinfo *renderer) +{ + renderer->entry_menu_active = FALSE; + + if (G_LIKELY (renderer->entry_menu_popdown_timer_id < 0)) + { + renderer->entry_menu_popdown_timer_id = g_timeout_add_full ( + G_PRIORITY_LOW, + 500u, + cell_diskinfo_renderer_entry_menu_popdown_timer, + renderer, + cell_diskinfo_renderer_entry_menu_popdown_timer_destroy); + } +} + +static gboolean +cell_diskinfo_renderer_entry_menu_popdown_timer (gpointer user_data) +{ + CellRendererDiskinfo *renderer = CELL_RENDERER_DISKINFO (user_data); + + GDK_THREADS_ENTER (); + + /* check if we still have the keyboard focus */ + if (G_UNLIKELY (!GTK_WIDGET_HAS_FOCUS (renderer->entry))) + cell_diskinfo_renderer_editing_done (GTK_CELL_EDITABLE (renderer->entry), renderer); + + GDK_THREADS_LEAVE (); + + return FALSE; +} + +static void +cell_diskinfo_renderer_entry_menu_popdown_timer_destroy (gpointer user_data) +{ + CELL_RENDERER_DISKINFO (user_data)->entry_menu_popdown_timer_id = -1; +} + +static void +get_volume_size_usage (const char *path, + int *used, + int *total) +{ + struct statfs sf; + long long total_size, used_size; + + if (-1 != statfs (path, &sf)) + { + total_size = ((long long)sf.f_blocks * sf.f_bsize) >> 20; + used_size = ((long long)(sf.f_blocks - sf.f_bavail) * sf.f_bsize) >> 20; + //g_print ("%ld, %ld : %d MB, %d MB\n", sf.f_blocks, sf.f_bavail, total_size, used_size); + } + else + { + total_size = used_size = 0; + } + + if (total) + *total = total_size; + if (used) + *used = used_size; +} + + +static void +cell_renderer_diskinfo_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *psec) +{ + CellRendererDiskinfo *celldiskinfo = CELL_RENDERER_DISKINFO (object); + + switch (param_id) + { + case PROP_USED: + g_value_set_int (value, celldiskinfo->used); + break; + + case PROP_TOTAL: + g_value_set_int (value, celldiskinfo->total); + break; + + case PROP_LABEL: + g_value_set_string (value, (gchar *)celldiskinfo->label); + break; + + case PROP_EDITABLE: + g_value_set_boolean (value, celldiskinfo->editable); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, psec); + break; + } +} + + +static void +cell_renderer_diskinfo_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + CellRendererDiskinfo *celldiskinfo = CELL_RENDERER_DISKINFO (object); + + switch (param_id) + { + case PROP_USED: + celldiskinfo->used = g_value_get_int (value); + break; + + case PROP_TOTAL: + celldiskinfo->total = g_value_get_int (value); + break; + + case PROP_LABEL: + g_free (celldiskinfo->label); + celldiskinfo->label = (gchar *) g_value_dup_string (value); + + /* parse disk infomation */ + if (celldiskinfo->label) + { + gchar **tokens; + + g_free (celldiskinfo->name); + g_free (celldiskinfo->exp_path); + celldiskinfo->name = NULL; + celldiskinfo->exp_path = NULL; + celldiskinfo->exp_used = 0; + celldiskinfo->exp_total = 0; + + tokens = g_strsplit_set (celldiskinfo->label, ";", -1); + if (tokens[0] != NULL) + celldiskinfo->name = g_strdup (tokens[0]); + + if (tokens[0] != NULL && tokens[1] != NULL) + { + int k; + celldiskinfo->exp_path = g_strdup (tokens[1]); + + /* calculate expansion path size */ + for (k=1; tokens[k] != NULL; k++) + { + int us, ts; + get_volume_size_usage (tokens[k], &us, &ts); + celldiskinfo->exp_used += us; + celldiskinfo->exp_total += ts; + } + } + + g_strfreev (tokens); + } + break; + + case PROP_EDITABLE: + celldiskinfo->editable = g_value_get_boolean (value); + if (celldiskinfo->editable) + GTK_CELL_RENDERER (celldiskinfo)->mode = GTK_CELL_RENDERER_MODE_EDITABLE; + else + GTK_CELL_RENDERER (celldiskinfo)->mode = GTK_CELL_RENDERER_MODE_INERT; + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec); + break; + } +} + +GtkCellRenderer * +cell_renderer_diskinfo_new (void) +{ + return g_object_new (TYPE_CELL_RENDERER_DISKINFO, NULL); +} + + +static void +cell_renderer_diskinfo_get_size (GtkCellRenderer *cell, + GtkWidget *widget, + GdkRectangle *cell_area, + gint *x_offset, + gint *y_offset, + gint *width, + gint *height) +{ + gint calc_width; + gint calc_height; + CellRendererDiskinfo *celldiskinfo = CELL_RENDERER_DISKINFO (cell); + + calc_width = (gint) cell->xpad * 2 + FIXED_WIDTH; + calc_height = (gint) cell->ypad * 2 + (celldiskinfo->exp_path ? FIXED_HEIGHT : FIXED_HEIGHT_TEXT); + + if (width) + *width = calc_width; + + if (height) + *height = calc_height; + + if (cell_area) + { + if (x_offset) + { + *x_offset = cell->xalign * (cell_area->width - calc_width); + *x_offset = MAX (*x_offset, 0); + } + + if (y_offset) + { + *y_offset = cell->yalign * (cell_area->height - calc_height); + *y_offset = MAX (*y_offset, 0); + } + } +} + +static void +add_attr (PangoAttrList *attr_list, + PangoAttribute *attr) +{ + attr->start_index = 0; + attr->end_index = G_MAXINT; + + pango_attr_list_insert (attr_list, attr); +} + +static PangoLayout* +get_layout (CellRendererDiskinfo *cell, + GtkWidget *widget, + const char *text, + TextRenderType type) +{ + PangoAttrList *attr_list; + PangoLayout *layout; + + layout = gtk_widget_create_pango_layout (widget, text); + + attr_list = pango_attr_list_new (); + + + if (widget->style && widget->style->font_desc) + add_attr (attr_list, pango_attr_font_desc_new (widget->style->font_desc)); + + if (TEXT_TYPE_HEADER == type) + { + add_attr (attr_list, pango_attr_weight_new (PANGO_WEIGHT_BOLD)); + add_attr (attr_list, pango_attr_scale_new (0.9)); + } + else if (TEXT_TYPE_STATUS == type) + { + add_attr (attr_list, pango_attr_scale_new (0.9)); + } + + pango_layout_set_single_paragraph_mode (layout, TRUE); + pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_NONE); + pango_layout_set_width (layout, -1); + pango_layout_set_wrap (layout, PANGO_WRAP_CHAR); + pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT); + + pango_layout_set_attributes (layout, attr_list); + pango_attr_list_unref (attr_list); + + return layout; +} + +static void +cell_renderer_diskinfo_render (GtkCellRenderer *cell, + GdkWindow *window, + GtkWidget *widget, + GdkRectangle *background_area, + GdkRectangle *cell_area, + GdkRectangle *expose_area, + guint flags) +{ + CellRendererDiskinfo *celldiskinfo = CELL_RENDERER_DISKINFO (cell); + GtkStateType state; + gint width, height; + gint x_offset, y_offset; + gint x, y, w; + gint freespace; + gdouble percentage; + gchar *detail; + PangoLayout *layout; + int used, total; + + used = celldiskinfo->used + celldiskinfo->exp_used; + total = celldiskinfo->total + celldiskinfo->exp_total; + //g_print ("%s (%p) - %d : %d\n", celldiskinfo->label, celldiskinfo, used, total); + + cell_renderer_diskinfo_get_size (cell, widget, cell_area, &x_offset, &y_offset, &width, &height); + + if (GTK_WIDGET_HAS_FOCUS (widget)) + state = GTK_STATE_ACTIVE; + else + state = GTK_STATE_NORMAL; + + + if (total != 0) + { + freespace = total - used; + percentage = (gdouble) used / (total + 1); + + detail = g_strdup_printf (_("%.1f %s free of %.1f %s"), + (freespace > 1024) ? (float)freespace/1024 : freespace, + (freespace > 1024) ? "GB" : "MB", + (total > 1024) ? (float)total/1024 : total, + (total > 1024) ? "GB" : "MB"); + + /* Drawing Stuff */ + width -= cell->xpad * 2; + height -= cell->ypad * 2; + + x = cell_area->x + x_offset + cell->xpad; + y = cell_area->y + y_offset + cell->ypad; + w = width; + + /* disk label */ + layout = get_layout (celldiskinfo, widget, celldiskinfo->name, TEXT_TYPE_HEADER); + gtk_paint_layout (widget->style, + window, + ((GTK_CELL_RENDERER_SELECTED & flags) ? GTK_STATE_ACTIVE : GTK_STATE_NORMAL), + TRUE, + NULL, + widget, + "label", + x, + y - 2, + layout); + g_object_unref (layout); + + /* progress border */ + gtk_paint_box (widget->style, + window, + GTK_STATE_NORMAL, GTK_SHADOW_IN, + NULL, widget, "border",//"trough", + x, + y + 16, + 124, + 16); + + /* progress percentage */ + if (used != 0) + { + gtk_paint_box (widget->style, + window, + state, GTK_SHADOW_OUT, + NULL, widget, "bar", + x, + y + 16, + 124 * percentage, + 16); + } + + /* usage status string */ + layout = get_layout (celldiskinfo, widget, detail, TEXT_TYPE_STATUS); + gtk_paint_layout (widget->style, + window, + ((GTK_CELL_RENDERER_SELECTED & flags) ? GTK_STATE_ACTIVE : GTK_STATE_NORMAL), + TRUE, + NULL, + widget, + "detail", + x, + y + 34, + layout); + + g_object_unref (layout); + + g_free (detail); + } + else + { + // Total is 0, show label only + gint text_w, text_h; + + layout = get_layout (celldiskinfo, widget, celldiskinfo->name, TEXT_TYPE_NORMAL); + pango_layout_get_pixel_size (layout, &text_w, &text_h); + + gtk_paint_layout (widget->style, + window, + ((GTK_CELL_RENDERER_SELECTED & flags) ? GTK_STATE_ACTIVE : GTK_STATE_NORMAL), + TRUE, + NULL, + widget, + "label", + cell_area->x + cell->xpad, + cell_area->y + cell->ypad + (cell_area->height - cell->ypad * 2 - text_h) / 2, + layout); + g_object_unref (layout); + } +} + diff -Nur thunar.git.1/thunar/cell-renderer-diskinfo.h thunar.git.addfunc/thunar/cell-renderer-diskinfo.h --- thunar.git.1/thunar/cell-renderer-diskinfo.h 1970-01-01 08:00:00.000000000 +0800 +++ thunar.git.addfunc/thunar/cell-renderer-diskinfo.h 2009-10-13 10:42:39.000000000 +0800 @@ -0,0 +1,57 @@ +#ifndef __CELL_RENDERER_DISKINFO_H__ +#define __CELL_RENDERER_DISKINFO_H__ + +#include + +/* Some boilerplate GObject type check and type cast macros. + * 'klass' is used here instead of 'class', because 'class' + * is a c++ keyword */ + +#define TYPE_CELL_RENDERER_DISKINFO (cell_renderer_diskinfo_get_type()) +#define CELL_RENDERER_DISKINFO(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_CELL_RENDERER_DISKINFO, CellRendererDiskinfo)) +#define CELL_RENDERER_DISKINFO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), TYPE_CELL_RENDERER_DISKINFO, CellRendererDiskinfoClass)) +#define IS_CELL_RENDERER_DISKINFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TYPE_CELL_RENDERER_DISKINFO)) +#define IS_CELL_RENDERER_DISKINFO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), TYPE_CELL_RENDERER_DISKINFO)) +#define CELL_RENDERER_DISKINFO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), TYPE_CELL_RENDERER_DISKINFO, CellRendererDiskinfoClass)) + +typedef struct _CellRendererDiskinfo CellRendererDiskinfo; +typedef struct _CellRendererDiskinfoClass CellRendererDiskinfoClass; + +struct _CellRendererDiskinfo +{ + GtkCellRenderer parent; + + gchar *label; + + gchar *name; + gint used; + gint total; + gchar *exp_path; + gint exp_used; + gint exp_total; + + /* cell editing support */ + gboolean editable; + GtkWidget *entry; + gboolean entry_menu_active; + gint entry_menu_popdown_timer_id; +}; + + +struct _CellRendererDiskinfoClass +{ + GtkCellRendererClass parent_class; + + void (*edited) (CellRendererDiskinfo *renderer, + const gchar *path, + const gchar *text); +}; + + +GType cell_renderer_diskinfo_get_type (void); + +GtkCellRenderer *cell_renderer_diskinfo_new (void); + + +#endif + diff -Nur thunar.git.1/thunar/Makefile.am thunar.git.addfunc/thunar/Makefile.am --- thunar.git.1/thunar/Makefile.am 2009-09-16 11:48:09.000000000 +0800 +++ thunar.git.addfunc/thunar/Makefile.am 2009-10-22 18:06:18.000000000 +0800 @@ -216,6 +216,10 @@ thunar-window.c \ thunar-window.h \ thunar-window-ui.h \ + cell-renderer-diskinfo.c \ + cell-renderer-diskinfo.h \ + shares.c \ + shares.h \ xfce-heading.c \ xfce-heading.h \ xfce-titled-dialog.c \ diff -Nur thunar.git.1/thunar/shares.c thunar.git.addfunc/thunar/shares.c --- thunar.git.1/thunar/shares.c 1970-01-01 08:00:00.000000000 +0800 +++ thunar.git.addfunc/thunar/shares.c 2009-10-22 18:56:06.000000000 +0800 @@ -0,0 +1,1136 @@ +/* + * Sebastien Estienne + * + * 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. + * + * (C) Copyright 2005 Ethium, Inc. + * (C) Copyright 2008-2009 Daniel Morales + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#ifdef HAVE_STRING_H +#include +#endif +#ifdef HAVE_TIME_H +#include +#endif +#ifdef HAVE_SYS_TYPES_H +#include +#endif +#ifdef HAVE_SYS_WAIT_H +#include +#endif + +#include +#include "shares.h" + +#undef DEBUG_SHARES +#ifdef DEBUG_SHARES +# define NET_USERSHARE_ARGV0 "debug-net-usershare" +#else +# define NET_USERSHARE_ARGV0 "net" +#endif + +static GHashTable *path_share_info_hash; +static GHashTable *share_name_share_info_hash; + +#define NUM_CALLS_BETWEEN_TIMESTAMP_UPDATES 10 /* default 100 */ +#define TIMESTAMP_THRESHOLD 1 /* default 10 seconds */ +static int refresh_timestamp_update_counter; +static time_t refresh_timestamp; + +#define KEY_PATH "path" +#define KEY_COMMENT "comment" +#define KEY_ACL "usershare_acl" +#define KEY_GUEST_OK "guest_ok" +#define GROUP_ALLOW_GUESTS "global" +#define KEY_ALLOW_GUESTS "usershare allow guests" + +/* Debugging flags */ +static gboolean throw_error_on_refresh; +static gboolean throw_error_on_add; +static gboolean throw_error_on_modify; +static gboolean throw_error_on_remove; + + + +/* Interface to "net usershare" */ + +static gboolean +net_usershare_run (int argc, char **argv, GKeyFile **ret_key_file, GError **error) +{ + int real_argc; + int i; + char **real_argv; + gboolean retval; + char *stdout_contents; + char *stderr_contents; + int exit_status; + int exit_code; + GKeyFile *key_file; + GError *real_error; + + g_assert (argc > 0); + g_assert (argv != NULL); + g_assert (error == NULL || *error == NULL); + + if (ret_key_file) + *ret_key_file = NULL; + + /* Build command line */ + + real_argc = 2 + argc + 1; /* "net" "usershare" [argv] NULL */ + real_argv = g_new (char *, real_argc); + + real_argv[0] = NET_USERSHARE_ARGV0; + real_argv[1] = "usershare"; + + for (i = 0; i < argc; i++) { + g_assert (argv[i] != NULL); + real_argv[i + 2] = argv[i]; + } + + real_argv[real_argc - 1] = NULL; + + /* Launch */ + + stdout_contents = NULL; + stderr_contents = NULL; + +#ifdef G_ENABLE_DEBUG + { + char **p; + + g_message ("------------------------------------------"); + + for (p = real_argv; *p; p++) + g_message ("spawn arg \"%s\"", *p); + + g_message ("end of spawn args; SPAWNING\n"); + } +#endif + + real_error = NULL; + retval = g_spawn_sync (NULL, /* cwd */ + real_argv, + NULL, /* envp */ + G_SPAWN_SEARCH_PATH, + NULL, /* GSpawnChildSetupFunc */ + NULL, /* user_data */ + &stdout_contents, + &stderr_contents, + &exit_status, + &real_error); + +#ifdef G_ENABLE_DEBUG + g_message ("returned from spawn: %s: %s", retval ? "SUCCESS" : "FAIL", retval ? "" : real_error->message); +#endif + + if (!retval) { + g_propagate_error (error, real_error); + goto out; + } + + if (!WIFEXITED (exit_status)) { +#ifdef G_ENABLE_DEBUG + g_message ("WIFEXITED(%d) was false!", exit_status); +#endif + retval = FALSE; + + if (WIFSIGNALED (exit_status)) { + int signal_num; + + signal_num = WTERMSIG (exit_status); +#ifdef G_ENABLE_DEBUG + g_message ("Child got signal %d", signal_num); +#endif + g_set_error (error, + SHARES_ERROR, + SHARES_ERROR_FAILED, + _("%s %s %s returned with signal %d"), + real_argv[0], + real_argv[1], + real_argv[2], + signal_num); + } else + g_set_error (error, + SHARES_ERROR, + SHARES_ERROR_FAILED, + _("%s %s %s failed for an unknown reason"), + real_argv[0], + real_argv[1], + real_argv[2]); + + goto out; + } + + exit_code = WEXITSTATUS (exit_status); +#ifdef G_ENABLE_DEBUG + g_message ("exit code %d", exit_code); +#endif + if (exit_code != 0) { + char *str; + char *message; + + /* stderr_contents is in the system locale encoding, not UTF-8 */ + + str = g_locale_to_utf8 (stderr_contents, -1, NULL, NULL, NULL); + + if (str && str[0]) + message = g_strdup_printf (_("'net usershare' returned error %d: %s"), exit_code, str); + else + message = g_strdup_printf (_("'net usershare' returned error %d"), exit_code); + + g_free (str); + + g_set_error (error, + G_SPAWN_ERROR, + G_SPAWN_ERROR_FAILED, + "%s", + message); + + g_free (message); + + retval = FALSE; + goto out; + } + + if (ret_key_file) { +#ifdef G_ENABLE_DEBUG + g_message ("caller wants GKeyFile"); +#endif + *ret_key_file = NULL; + + /* FIXME: jeallison@novell.com says the output of "net usershare" is nearly always + * in UTF-8, but that it can be configured in the master smb.conf. We assume + * UTF-8 for now. + */ + + if (!g_utf8_validate (stdout_contents, -1, NULL)) { +#ifdef G_ENABLE_DEBUG + g_message ("stdout of net usershare was not in valid UTF-8"); +#endif + g_set_error (error, + G_SPAWN_ERROR, + G_SPAWN_ERROR_FAILED, + _("the output of 'net usershare' is not in valid UTF-8 encoding")); + retval = FALSE; + goto out; + } + + key_file = g_key_file_new (); + + real_error = NULL; + if (!g_key_file_load_from_data (key_file, stdout_contents, -1, 0, &real_error)) { +#ifdef G_ENABLE_DEBUG + g_message ("Error when parsing key file {\n%s\n}: %s", stdout_contents, real_error->message); +#endif + g_propagate_error (error, real_error); + g_key_file_free (key_file); + retval = FALSE; + goto out; + } + + retval = TRUE; + *ret_key_file = key_file; + } else + retval = TRUE; +#ifdef G_ENABLE_DEBUG + g_message ("success from calling net usershare and parsing its output"); +#endif + out: + g_free (real_argv); + g_free (stdout_contents); + g_free (stderr_contents); +#ifdef G_ENABLE_DEBUG + g_message ("------------------------------------------"); +#endif + return retval; +} + + + +/* Internals */ + +static void +ensure_hashes (void) +{ + if (path_share_info_hash == NULL) { + g_assert (share_name_share_info_hash == NULL); + + path_share_info_hash = g_hash_table_new (g_str_hash, g_str_equal); + share_name_share_info_hash = g_hash_table_new (g_str_hash, g_str_equal); + } else + g_assert (share_name_share_info_hash != NULL); +} + +static ShareInfo * +lookup_share_by_path (const char *path) +{ + ensure_hashes (); + return g_hash_table_lookup (path_share_info_hash, path); +} + +static ShareInfo * +lookup_share_by_share_name (const char *share_name) +{ + ensure_hashes (); + return g_hash_table_lookup (share_name_share_info_hash, share_name); +} + +static void +add_share_info_to_hashes (ShareInfo *info) +{ + ensure_hashes (); + g_hash_table_insert (path_share_info_hash, info->path, info); + g_hash_table_insert (share_name_share_info_hash, info->share_name, info); +} + +static void +remove_share_info_from_hashes (ShareInfo *info) +{ + ensure_hashes (); + g_hash_table_remove (path_share_info_hash, info->path); + g_hash_table_remove (share_name_share_info_hash, info->share_name); +} + +static gboolean +remove_from_path_hash_cb (gpointer key, + gpointer value, + gpointer data) +{ + ShareInfo *info; + + info = value; + shares_free_share_info (info); + + return TRUE; +} + +static gboolean +remove_from_share_name_hash_cb (gpointer key, + gpointer value, + gpointer data) +{ + /* The ShareInfo was already freed in remove_from_path_hash_cb() */ + return TRUE; +} + +static char * +get_string_from_key_file (GKeyFile *key_file, const char *group, const char *key) +{ + GError *error; + char *str; + + error = NULL; + str = NULL; + + if (g_key_file_has_key (key_file, group, key, &error)) { + str = g_key_file_get_string (key_file, group, key, &error); + if (!str) { + g_assert (!g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_NOT_FOUND) + && !g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)); + + g_error_free (error); + } + } else { + g_assert (!g_error_matches (error, G_KEY_FILE_ERROR, G_KEY_FILE_ERROR_GROUP_NOT_FOUND)); + g_error_free (error); + } + + return str; +} + +static void +add_key_group_to_hashes (GKeyFile *key_file, const char *group) +{ + char *path; + char *comment; + char *acl; + gboolean is_writable; + char *guest_ok_str; + gboolean guest_ok; + ShareInfo *info; + ShareInfo *old_info; + + /* Remove the old share based on the name */ + + old_info = lookup_share_by_share_name (group); + if (old_info) { + remove_share_info_from_hashes (old_info); + shares_free_share_info (old_info); + } + + /* Start parsing, and remove the old share based on the path */ + + path = get_string_from_key_file (key_file, group, KEY_PATH); + if (!path) { +#ifdef G_ENABLE_DEBUG + g_message ("group '%s' doesn't have a '%s' key! Ignoring group.", group, KEY_PATH); +#endif + return; + } + + old_info = lookup_share_by_path (path); + if (old_info) { + remove_share_info_from_hashes (old_info); + shares_free_share_info (old_info); + } + + /* Finish parsing */ + + comment = get_string_from_key_file (key_file, group, KEY_COMMENT); + + acl = get_string_from_key_file (key_file, group, KEY_ACL); + if (acl) { + if (strncmp (acl, "Everyone:R", 10) == 0) + is_writable = FALSE; + else if (strncmp (acl, "Everyone:F", 10) == 0) + is_writable = TRUE; + else { +#ifdef G_ENABLE_DEBUG + g_message ("unknown format for key '%s/%s' as it contains '%s'. Assuming that the share is read-only", + group, KEY_ACL, acl); +#endif + is_writable = FALSE; + } + + g_free (acl); + } else { +#ifdef G_ENABLE_DEBUG + g_message ("group '%s' doesn't have a '%s' key! Assuming that the share is read-only.", group, KEY_ACL); +#endif + is_writable = FALSE; + } + + guest_ok_str = get_string_from_key_file (key_file, group, KEY_GUEST_OK); + if (guest_ok_str) { + if (strcmp (guest_ok_str, "n") == 0) + guest_ok = FALSE; + else if (strcmp (guest_ok_str, "y") == 0) + guest_ok = TRUE; + else { +#ifdef G_ENABLE_DEBUG + g_message ("unknown format for key '%s/%s' as it contains '%s'. Assuming that the share is not guest accessible.", + group, KEY_GUEST_OK, guest_ok_str); +#endif + guest_ok = FALSE; + } + + g_free (guest_ok_str); + } else { +#ifdef G_ENABLE_DEBUG + g_message ("group '%s' doesn't have a '%s' key! Assuming that the share is not guest accessible.", group, KEY_GUEST_OK); +#endif + guest_ok = FALSE; + } + + g_assert (path != NULL); + g_assert (group != NULL); + + info = g_new (ShareInfo, 1); + info->path = path; + info->share_name = g_strdup (group); + info->comment = comment; + info->is_writable = is_writable; + info->guest_ok = guest_ok; + + add_share_info_to_hashes (info); +} + +static void +replace_shares_from_key_file (GKeyFile *key_file) +{ + gsize num_groups; + char **group_names; + gsize i; + + group_names = g_key_file_get_groups (key_file, &num_groups); + + /* FIXME: In add_key_group_to_hashes(), we simply ignore key groups + * which have invalid data (i.e. no path). We could probably accumulate a + * GError with the list of invalid groups and propagate it upwards. + */ + for (i = 0; i < num_groups; i++) { + g_assert (group_names[i] != NULL); + add_key_group_to_hashes (key_file, group_names[i]); + } + + g_strfreev (group_names); +} + +static gboolean +refresh_shares (GError **error) +{ + GKeyFile *key_file; + char *argv[1]; + GError *real_error; + + free_all_shares (); + + if (throw_error_on_refresh) { + g_set_error (error, + SHARES_ERROR, + SHARES_ERROR_FAILED, + _("Failed")); + return FALSE; + } + + argv[0] = "info"; + + real_error = NULL; + if (!net_usershare_run (G_N_ELEMENTS (argv), argv, &key_file, &real_error)) { +#ifdef G_ENABLE_DEBUG + g_message ("Called \"net usershare info\" but it failed: %s", real_error->message); +#endif + g_propagate_error (error, real_error); + return FALSE; + } + + g_assert (key_file != NULL); + + replace_shares_from_key_file (key_file); + g_key_file_free (key_file); + + return TRUE; +} + +static gboolean +refresh_if_needed (GError **error) +{ + gboolean retval; + + if (refresh_timestamp_update_counter == 0) { + time_t new_timestamp; + + new_timestamp = time (NULL); + if (new_timestamp - refresh_timestamp > TIMESTAMP_THRESHOLD) { +#ifdef G_ENABLE_DEBUG + g_message ("REFRESHING SHARES"); +#endif + retval = refresh_shares (error); + } else + retval = TRUE; + + if (retval) + { + refresh_timestamp = new_timestamp; + + refresh_timestamp_update_counter = NUM_CALLS_BETWEEN_TIMESTAMP_UPDATES; + } + } else { + refresh_timestamp_update_counter--; + retval = TRUE; + } + + return retval; +} + +static ShareInfo * +copy_share_info (ShareInfo *info) +{ + ShareInfo *copy; + + if (!info) + return NULL; + + copy = g_new (ShareInfo, 1); + copy->path = g_strdup (info->path); + copy->share_name = g_strdup (info->share_name); + copy->comment = g_strdup (info->comment); + copy->is_writable = info->is_writable; + copy->guest_ok = info->guest_ok; + + return copy; +} + +static gboolean +test_param (const gchar *testparam, gboolean *supports_test_ret, GError **error) +{ + gboolean retval; + gboolean result; + char *stdout_contents; + char *stderr_contents; + int exit_status; + int exit_code; + + *supports_test_ret = FALSE; + + result = g_spawn_command_line_sync (testparam, + &stdout_contents, + &stderr_contents, + &exit_status, + error); + if (!result) + return FALSE; + + retval = FALSE; + + if (!WIFEXITED (exit_status)) { + if (WIFSIGNALED (exit_status)) { + int signal_num; + + signal_num = WTERMSIG (exit_status); + g_set_error (error, + SHARES_ERROR, + SHARES_ERROR_FAILED, + _("Samba's testparm returned with signal %d"), + signal_num); + } else + g_set_error (error, + SHARES_ERROR, + SHARES_ERROR_FAILED, + _("Samba's testparm failed for an unknown reason")); + + goto out; + } + + exit_code = WEXITSTATUS (exit_status); + if (exit_code != 0) { + char *str; + char *message; + + /* stderr_contents is in the system locale encoding, not UTF-8 */ + + str = g_locale_to_utf8 (stderr_contents, -1, NULL, NULL, NULL); + + if (str && str[0]) + message = g_strdup_printf (_("Samba's testparm returned error %d: %s"), exit_code, str); + else + message = g_strdup_printf (_("Samba's testparm returned error %d"), exit_code); + + g_free (str); + + g_set_error (error, + G_SPAWN_ERROR, + G_SPAWN_ERROR_FAILED, + "%s", + message); + + g_free (message); + + goto out; + } + + retval = TRUE; + *supports_test_ret = (g_ascii_strncasecmp (stdout_contents, "Yes", 3) == 0); + + out: + g_free (stdout_contents); + g_free (stderr_contents); + + return retval; +} + +/** + * shares_supports_guest_ok: + * @supports_guest_ok_ret: Location to store whether "usershare allow guests" + * is enabled. + * @error: Location to store error, or #NULL. + * + * Determines whether the option "usershare allow guests" is enabled in samba + * config as shown by testparm. + * + * Return value: #TRUE if if the info could be queried successfully, #FALSE + * otherwise. If this function returns #FALSE, an error code will be returned + * in the @error argument, and *@supports_guest_ok_ret will be set to #FALSE. ++ **/ +gboolean +shares_supports_guest_ok (gboolean *supports_guest_ok_ret, GError **error) +{ + return test_param ("testparm -s --parameter-name='usershare allow guests'", + supports_guest_ok_ret, + error); +} + +/** + * shares_has_owner_only: + * @supports_owner_only_ret: Location to store whether "usershare owner only" + * is enabled. + * @error: Location to store error, or #NULL. + * + * Determines whether the option "usershare usershare owner only" is enabled in samba + * config as shown by testparm. + * + * Return value: #TRUE if if the info could be queried successfully, #FALSE + * otherwise. If this function returns #FALSE, an error code will be returned + * in the @error argument, and *@supports_owner_only_ret will be set to #FALSE. + **/ +gboolean +shares_has_owner_only (gboolean *supports_owner_only_ret, + GError **error) +{ + return test_param ("testparm -s --parameter-name='usershare owner only'", + supports_owner_only_ret, + error); +} + +static gboolean +add_share (ShareInfo *info, GError **error) +{ + char *argv[7]; + int argc; + ShareInfo *copy; + GKeyFile *key_file; + GError *real_error; + gboolean supports_success; + gboolean supports_guest_ok; +#ifdef G_ENABLE_DEBUG + g_message ("add_share() start"); +#endif + if (throw_error_on_add) { + g_set_error (error, + SHARES_ERROR, + SHARES_ERROR_FAILED, + _("Failed")); +#ifdef G_ENABLE_DEBUG + g_message ("add_share() end FAIL"); +#endif + return FALSE; + } + + supports_success = shares_supports_guest_ok (&supports_guest_ok, error); + if (!supports_success) + return FALSE; + + argv[0] = "add"; + argv[1] = "-l"; + argv[2] = info->share_name; + argv[3] = info->path; + argv[4] = info->comment; + argv[5] = info->is_writable ? "Everyone:F" : "Everyone:R"; + + if (supports_guest_ok) { + argv[6] = info->guest_ok ? "guest_ok=y" : "guest_ok=n"; + argc = 7; + } else + argc = 6; + + real_error = NULL; + if (!net_usershare_run (argc, argv, &key_file, &real_error)) { +#ifdef G_ENABLE_DEBUG + g_message ("Called \"net usershare add\" but it failed: %s", real_error->message); +#endif + g_propagate_error (error, real_error); + return FALSE; + } + + replace_shares_from_key_file (key_file); + + copy = copy_share_info (info); + add_share_info_to_hashes (copy); +#ifdef G_ENABLE_DEBUG + g_message ("add_share() end SUCCESS"); +#endif + return TRUE; +} + +static gboolean +remove_share (const char *path, GError **error) +{ + ShareInfo *old_info; + char *argv[2]; + GError *real_error; +#ifdef G_ENABLE_DEBUG + g_message ("remove_share() start"); +#endif + if (throw_error_on_remove) { + g_set_error (error, + SHARES_ERROR, + SHARES_ERROR_FAILED, + "Failed"); +#ifdef G_ENABLE_DEBUG + g_message ("remove_share() end FAIL"); +#endif + return FALSE; + } + + old_info = lookup_share_by_path (path); + if (!old_info) { + char *display_name; + + display_name = g_filename_display_name (path); + g_set_error (error, + SHARES_ERROR, + SHARES_ERROR_NONEXISTENT, + _("Cannot remove the share for path %s: that path is not shared"), + display_name); + g_free (display_name); +#ifdef G_ENABLE_DEBUG + g_message ("remove_share() end FAIL: path %s was not in our hashes", path); +#endif + return FALSE; + } + + argv[0] = "delete"; + argv[1] = old_info->share_name; + + real_error = NULL; + if (!net_usershare_run (G_N_ELEMENTS (argv), argv, NULL, &real_error)) { +#ifdef G_ENABLE_DEBUG + g_message ("Called \"net usershare delete\" but it failed: %s", real_error->message); +#endif + g_propagate_error (error, real_error); +#ifdef G_ENABLE_DEBUG + g_message ("remove_share() end FAIL"); +#endif + return FALSE; + } + + remove_share_info_from_hashes (old_info); + shares_free_share_info (old_info); +#ifdef G_ENABLE_DEBUG + g_message ("remove_share() end SUCCESS"); +#endif + return TRUE; +} + +static gboolean +modify_share (const char *old_path, ShareInfo *info, GError **error) +{ + ShareInfo *old_info; +#ifdef G_ENABLE_DEBUG + g_message ("modify_share() start"); +#endif + old_info = lookup_share_by_path (old_path); + if (old_info == NULL) { +#ifdef G_ENABLE_DEBUG + g_message ("modify_share() end; calling add_share() instead"); +#endif + return add_share (info, error); + } + + g_assert (old_info != NULL); + + if (strcmp (info->path, old_info->path) != 0) { + g_set_error (error, + SHARES_ERROR, + SHARES_ERROR_FAILED, + _("Cannot change the path of an existing share; please remove the old share first and add a new one")); +#ifdef G_ENABLE_DEBUG + g_message ("modify_share() end FAIL: tried to change the path in a share!"); +#endif + return FALSE; + } + + if (throw_error_on_modify) { + g_set_error (error, + SHARES_ERROR, + SHARES_ERROR_FAILED, + "Failed"); +#ifdef G_ENABLE_DEBUG + g_message ("modify_share() end FAIL"); +#endif + return FALSE; + } + + /* Although "net usershare add" will modify an existing share if it has the same share name + * as the one that gets passed in, our semantics are different. We have a one-to-one mapping + * between paths and share names; "net usershare" supports a one-to-many mapping from paths + * to share names. So, we must first remove the old share and then add the new/modified one. + */ + + if (!remove_share (old_path, error)) { +#ifdef G_ENABLE_DEBUG + g_message ("modify_share() end FAIL: error when removing old share"); +#endif + return FALSE; + } +#ifdef G_ENABLE_DEBUG + g_message ("modify_share() end: will call add_share() with the new share info"); +#endif + return add_share (info, error); +} + + + +/* Public API */ + +GQuark +shares_error_quark (void) +{ + static GQuark quark; + + if (quark == 0) + quark = g_quark_from_string ("nautilus-shares-error-quark"); /* not from_static_string since we are a module */ + + return quark; +} + +/** + * shares_free_share_info: + * @info: A #ShareInfo structure. + * + * Frees a #ShareInfo structure. + **/ +void +shares_free_share_info (ShareInfo *info) +{ + g_assert (info != NULL); + + g_free (info->path); + g_free (info->share_name); + g_free (info->comment); + g_free (info); +} + +/** + * shares_get_path_is_shared: + * @path: A full path name ("/foo/bar/baz") in file system encoding. + * @ret_is_shared: Location to store result value (#TRUE if the path is shared, #FALSE otherwise) + * @error: Location to store error, or #NULL. + * + * Checks whether a path is shared through Samba. + * + * Return value: #TRUE if the info could be queried successfully, #FALSE + * otherwise. If this function returns #FALSE, an error code will be returned in the + * @error argument, and *@ret_is_shared will be set to #FALSE. + **/ +gboolean +shares_get_path_is_shared (const char *path, gboolean *ret_is_shared, GError **error) +{ + g_assert (ret_is_shared != NULL); + g_assert (error == NULL || *error == NULL); + + if (!refresh_if_needed (error)) { +#if 0 + /* make it refresh by real time */ + if(!refresh_shares (error)){ +#endif + *ret_is_shared = FALSE; + return FALSE; + } + + *ret_is_shared = (lookup_share_by_path (path) != NULL); + + return TRUE; +} + +/** + * shares_get_share_info_for_path: + * @path: A full path name ("/foo/bar/baz") in file system encoding. + * @ret_share_info: Location to store result with the share's info - on return, + * will be non-NULL if the path is indeed shared, or #NULL if the path is not + * shared. You must free the non-NULL value with shares_free_share_info(). + * @error: Location to store error, or #NULL. + * + * Queries the information for a shared path: its share name, its read-only status, etc. + * + * Return value: #TRUE if the info could be queried successfully, #FALSE + * otherwise. If this function returns #FALSE, an error code will be returned in the + * @error argument, and *@ret_share_info will be set to #NULL. + **/ +gboolean +shares_get_share_info_for_path (const char *path, ShareInfo **ret_share_info, GError **error) +{ + ShareInfo *info; + + g_assert (path != NULL); + g_assert (ret_share_info != NULL); + g_assert (error == NULL || *error == NULL); + + if (!refresh_if_needed (error)) { + *ret_share_info = NULL; + return FALSE; + } + + info = lookup_share_by_path (path); + *ret_share_info = copy_share_info (info); + + return TRUE; +} + +/** + * shares_get_share_name_exists: + * @share_name: Name of a share. + * @ret_exists: Location to store return value; #TRUE if the share name exists, #FALSE otherwise. + * + * Queries whether a share name already exists in the user's list of shares. + * + * Return value: #TRUE if the info could be queried successfully, #FALSE + * otherwise. If this function returns #FALSE, an error code will be returned in the + * @error argument, and *@ret_exists will be set to #FALSE. + **/ +gboolean +shares_get_share_name_exists (const char *share_name, gboolean *ret_exists, GError **error) +{ + g_assert (share_name != NULL); + g_assert (ret_exists != NULL); + g_assert (error == NULL || *error == NULL); + + if (!refresh_if_needed (error)) { + *ret_exists = FALSE; + return FALSE; + } + + *ret_exists = (lookup_share_by_share_name (share_name) != NULL); + + return TRUE; +} + +/** + * shares_get_share_info_for_share_name: + * @share_name: Name of a share. + * @ret_share_info: Location to store result with the share's info - on return, + * will be non-NULL if there is a share for the specified name, or #NULL if no + * share has such name. You must free the non-NULL value with + * shares_free_share_info(). + * @error: Location to store error, or #NULL. + * + * Queries the information for the share which has a specific name. + * + * Return value: #TRUE if the info could be queried successfully, #FALSE + * otherwise. If this function returns #FALSE, an error code will be returned in the + * @error argument, and *@ret_share_info will be set to #NULL. + **/ +gboolean +shares_get_share_info_for_share_name (const char *share_name, ShareInfo **ret_share_info, GError **error) +{ + ShareInfo *info; + + g_assert (share_name != NULL); + g_assert (ret_share_info != NULL); + g_assert (error == NULL || *error == NULL); + + if (!refresh_if_needed (error)) { + *ret_share_info = NULL; + return FALSE; + } + + info = lookup_share_by_share_name (share_name); + *ret_share_info = copy_share_info (info); + + return TRUE; +} + +/** + * shares_modify_share: + * @old_path: Path of the share to modify, or %NULL. + * @info: Info of the share to modify/add, or %NULL to delete a share. + * @error: Location to store error, or #NULL. + * + * Can add, modify, or delete shares. To add a share, pass %NULL for @old_path, + * and a non-null @info. To modify a share, pass a non-null @old_path and + * non-null @info; in this case, @info->path must have the same contents as + * @old_path. To remove a share, pass a non-NULL @old_path and a %NULL @info. + * + * Return value: TRUE if the share could be modified, FALSE otherwise. If this returns + * FALSE, then the error information will be placed in @error. + **/ +gboolean +shares_modify_share (const char *old_path, ShareInfo *info, GError **error) +{ + g_assert ((old_path == NULL && info != NULL) + || (old_path != NULL && info == NULL) + || (old_path != NULL && info != NULL)); + g_assert (error == NULL || *error == NULL); + + if (!refresh_if_needed (error)) + return FALSE; + + if (old_path == NULL) + return add_share (info, error); + else if (info == NULL) + return remove_share (old_path, error); + else + return modify_share (old_path, info, error); +} + +static void +copy_to_slist_cb (gpointer key, gpointer value, gpointer data) +{ + ShareInfo *info; + ShareInfo *copy; + GSList **list; + + info = value; + list = data; + + copy = copy_share_info (info); + *list = g_slist_prepend (*list, copy); +} + +/** + * shares_get_share_info_list: + * @ret_info_list: Location to store the return value, which is a list + * of #ShareInfo structures. Free this with shares_free_share_info_list(). + * @error: Location to store error, or #NULL. + * + * Gets the list of shared folders and their information. + * + * Return value: #TRUE if the info could be queried successfully, #FALSE + * otherwise. If this function returns #FALSE, an error code will be returned in the + * @error argument, and *@ret_info_list will be set to #NULL. + **/ +gboolean +shares_get_share_info_list (GSList **ret_info_list, GError **error) +{ + g_assert (ret_info_list != NULL); + g_assert (error == NULL || *error == NULL); + + if (!refresh_if_needed (error)) { + *ret_info_list = NULL; + return FALSE; + } + + *ret_info_list = NULL; + g_hash_table_foreach (path_share_info_hash, copy_to_slist_cb, ret_info_list); + + return TRUE; +} + +/** + * shares_free_share_info_list: + * @list: List of #ShareInfo structures, or %NULL. + * + * Frees a list of #ShareInfo structures as returned by shares_get_share_info_list(). + **/ +void +shares_free_share_info_list (GSList *list) +{ + GSList *l; + + for (l = list; l; l = l->next) { + ShareInfo *info; + + info = l->data; + shares_free_share_info (l->data); + } + + g_slist_free (list); +} + +void +free_all_shares (void) +{ + ensure_hashes (); + g_hash_table_foreach_remove (path_share_info_hash, remove_from_path_hash_cb, NULL); + g_hash_table_foreach_remove (share_name_share_info_hash, remove_from_share_name_hash_cb, NULL); +} + +void +shares_set_debug (gboolean error_on_refresh, + gboolean error_on_add, + gboolean error_on_modify, + gboolean error_on_remove) +{ + throw_error_on_refresh = error_on_refresh; + throw_error_on_add = error_on_add; + throw_error_on_modify = error_on_modify; + throw_error_on_remove = error_on_remove; +} diff -Nur thunar.git.1/thunar/shares.h thunar.git.addfunc/thunar/shares.h --- thunar.git.1/thunar/shares.h 1970-01-01 08:00:00.000000000 +0800 +++ thunar.git.addfunc/thunar/shares.h 2009-10-13 10:42:26.000000000 +0800 @@ -0,0 +1,73 @@ +/* nautilus-share -- Nautilus File Sharing Extension + * + * Sebastien Estienne + * + * 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. + * + * (C) Copyright 2005 Ethium, Inc. + */ + +#ifndef SHARES_H +#define SHARES_H + +#include + +typedef struct { + char *path; + char *share_name; + char *comment; + gboolean is_writable; + gboolean guest_ok; +} ShareInfo; + +#define SHARES_ERROR (shares_error_quark ()) + +typedef enum { + SHARES_ERROR_FAILED, + SHARES_ERROR_NONEXISTENT +} SharesError; + +void free_all_shares (void); + +GQuark shares_error_quark (void); + +void shares_free_share_info (ShareInfo *info); + +gboolean shares_get_path_is_shared (const char *path, gboolean *ret_is_shared, GError **error); + +gboolean shares_get_share_info_for_path (const char *path, ShareInfo **ret_share_info, GError **error); + +gboolean shares_get_share_name_exists (const char *share_name, gboolean *ret_exists, GError **error); + +gboolean shares_get_share_info_for_share_name (const char *share_name, ShareInfo **ret_share_info, GError **error); + +gboolean shares_modify_share (const char *old_path, ShareInfo *info, GError **error); + +gboolean shares_get_share_info_list (GSList **ret_info_list, GError **error); + +void shares_free_share_info_list (GSList *list); + +gboolean shares_supports_guest_ok (gboolean *supports_guest_ok_ret, + GError **error); + +gboolean shares_has_owner_only (gboolean *supports_owner_only_ret, + GError **error); + +void shares_set_debug (gboolean error_on_refresh, + gboolean error_on_add, + gboolean error_on_modify, + gboolean error_on_remove); + +#endif diff -Nur thunar.git.1/thunar/thunar-application.c thunar.git.addfunc/thunar/thunar-application.c --- thunar.git.1/thunar/thunar-application.c 2009-09-16 11:48:09.000000000 +0800 +++ thunar.git.addfunc/thunar/thunar-application.c 2009-10-21 19:06:35.000000000 +0800 @@ -101,6 +101,13 @@ static void thunar_application_drive_eject (GVolumeMonitor *volume_monitor, GDrive *drive, ThunarApplication *application); +static void thunar_application_volume_removed (GVolumeMonitor *volume_monitor, + GVolume *volume, + ThunarApplication *application); +static void thunar_application_mount_removed (GVolumeMonitor *volume_monitor, + GMount *mount, + ThunarApplication *application); + static gboolean thunar_application_volman_idle (gpointer user_data); static void thunar_application_volman_idle_destroy (gpointer user_data); static void thunar_application_volman_watch (GPid pid, @@ -111,6 +118,13 @@ static void thunar_application_show_dialogs_destroy (gpointer user_data); static void thunar_application_process_files (ThunarApplication *application); +static gboolean thunar_application_notify_send_function (int func); +static gchar *thunar_application_create_notification_cmd(int level, + int timeout, + const char *icon, + const char *summary, + const char *message); + struct _ThunarApplicationClass @@ -213,6 +227,9 @@ g_signal_connect (application->volume_monitor, "drive-connected", G_CALLBACK (thunar_application_drive_connected), application); g_signal_connect (application->volume_monitor, "drive-disconnected", G_CALLBACK (thunar_application_drive_disconnected), application); g_signal_connect (application->volume_monitor, "drive-eject-button", G_CALLBACK (thunar_application_drive_eject), application); + g_signal_connect (application->volume_monitor, "volume-removed", G_CALLBACK (thunar_application_volume_removed), application); + g_signal_connect (application->volume_monitor, "mount-removed", G_CALLBACK (thunar_application_mount_removed), application); + } @@ -391,9 +408,34 @@ GtkWidget *dialog; GdkScreen *screen; ThunarJob *job; + gboolean diff_media = FALSE; _thunar_return_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent)); + /* check if any of src & dst is in removable device */ + if (source_file_list && source_file_list->data && target_file_list && target_file_list->data) + { + ThunarFile *src, *dst; + + /* Tries to query the file referred to by src & dst */ + src = thunar_file_get (source_file_list->data, NULL); + dst = thunar_file_get (target_file_list->data, NULL); + if (src && dst) + { + GVolume *src_vol, *dst_vol; + + /* Determines whether src_vol & dst_vol is a removable device */ + src_vol = thunar_file_get_volume (src); + dst_vol = thunar_file_get_volume (dst); + diff_media = (src_vol && thunar_g_volume_is_removable (src_vol)) || + (dst_vol && thunar_g_volume_is_removable (dst_vol)); + } + if (src) + g_object_unref (src); + if (dst) + g_object_unref (dst); + } + /* parse the parent pointer */ screen = thunar_util_parse_parent (parent, NULL); @@ -436,7 +478,11 @@ application, thunar_application_show_dialogs_destroy); } } - + + /* sync data */ + if(diff_media){ + sync (); + } /* drop our reference on the job */ g_object_unref (job); } @@ -496,24 +542,99 @@ } } +/* make the detail of notify in addition send notify*/ +static gboolean +thunar_application_notify_send_function (int func) +{ + gboolean ret = FALSE; + gchar *cmd = NULL; + + /* set the detail of unsafe notify */ + if (func == 0) + { + const char *summary = _("Unsafe device removal"); + const char *message = _("To avoid serious data loss, disable removable " + "device with the 'Safely Remove' white eject button.\n"); + cmd = thunar_application_create_notification_cmd (1, 6000, "gnome-dev-removable", summary, message); + } + + /* exec notify send command */ + if (NULL != cmd) + { + ret = (-1 != system (cmd)); + g_free (cmd); + } + + return ret; +} + +/* add command of notify-send to notify command */ +static gchar * +thunar_application_create_notification_cmd (int level, + int timeout, + const char *icon, + const char *summary, + const char *message) +{ + /* init notify level */ + const char *level_str[] = { "critical", "normal", "low" }; + char *cmd; + + /* set notify level */ + cmd = g_strdup_printf ( + "notify-send -i %s -u %s -t %d \"%s\" \"%s\"", + icon, level_str[level], timeout, summary, message); + return cmd; +} static void +thunar_application_mount_removed (GVolumeMonitor *volume_monitor, + GMount *mount, + ThunarApplication *application) +{ + /* null */ +} + +static void +thunar_application_volume_removed (GVolumeMonitor *volume_monitor, + GVolume *volume, + ThunarApplication *application) +{ + /* null */ +} +static void thunar_application_drive_disconnected (GVolumeMonitor *volume_monitor, GDrive *drive, ThunarApplication *application) { GSList *lp; gchar *udi; + GVolume *volume; _thunar_return_if_fail (G_IS_VOLUME_MONITOR (volume_monitor)); _thunar_return_if_fail (application->volume_monitor == volume_monitor); _thunar_return_if_fail (G_IS_DRIVE (drive)); _thunar_return_if_fail (THUNAR_IS_APPLICATION (application)); - + /* determine the HAL UDI for this drive */ udi = g_drive_get_identifier (drive, G_VOLUME_IDENTIFIER_KIND_HAL_UDI); + /* check if unsafe remove */ + if (g_drive_has_volumes (drive)) + { + /* look for volumes */ + for (lp = g_drive_get_volumes(drive); lp != NULL; lp = lp->next) + { + volume = lp->data; + /* check volume be mounted */ + if (g_volume_get_mount(volume)) + { + thunar_application_notify_send_function (0); + } + } + } + /* check if we have a UDI */ if (G_LIKELY (udi != NULL)) { diff -Nur thunar.git.1/thunar/thunar-file.c thunar.git.addfunc/thunar/thunar-file.c --- thunar.git.1/thunar/thunar-file.c 2009-09-16 11:48:09.000000000 +0800 +++ thunar.git.addfunc/thunar/thunar-file.c 2009-10-22 18:09:04.000000000 +0800 @@ -64,6 +64,7 @@ #include #include +#include /* Additional flags associated with a ThunarFile */ @@ -2463,6 +2464,16 @@ */ emblems = g_list_prepend (emblems, THUNAR_FILE_EMBLEM_NAME_CANT_WRITE); } + else if (thunar_file_is_directory (file)) + { + gboolean is_shared; + gchar * location = thunar_g_file_get_location(file->gfile); + shares_get_path_is_shared (location, &is_shared, NULL); + /* Check if file is directory and be shared */ + if(is_shared){ + emblems = g_list_prepend (emblems, "emblem-shared"); + } + } return emblems; } diff -Nur thunar.git.1/thunar/thunar-launcher.c thunar.git.addfunc/thunar/thunar-launcher.c --- thunar.git.1/thunar/thunar-launcher.c 2009-09-16 11:48:09.000000000 +0800 +++ thunar.git.addfunc/thunar/thunar-launcher.c 2009-10-22 18:11:58.000000000 +0800 @@ -1170,13 +1170,25 @@ ThunarLauncherPokeData *poke_data; GAppInfo *app_info; GList *selected_paths; - + const gchar *content_type; _thunar_return_if_fail (GTK_IS_ACTION (action)); _thunar_return_if_fail (THUNAR_IS_LAUNCHER (launcher)); /* check if we have a mime handler associated with the action */ app_info = g_object_get_qdata (G_OBJECT (action), thunar_launcher_handler_quark); - if (G_LIKELY (app_info != NULL)) + + /* fix can't open network folder */ + if (g_list_length (launcher->selected_files) == 1) + { + ThunarFile *thunar_file = launcher->selected_files->data; + if (G_LIKELY (thunar_file != NULL) ) + { + content_type = g_file_info_get_content_type(thunar_file->info); + } + g_object_unref (thunar_file); + } + + if (G_LIKELY (app_info != NULL) && strcmp("inode/directory",content_type)) { /* try to open the selected files using the given application */ selected_paths = thunar_file_list_to_thunar_g_file_list (launcher->selected_files); diff -Nur thunar.git.1/thunar/thunar-shortcuts-model.c thunar.git.addfunc/thunar/thunar-shortcuts-model.c --- thunar.git.1/thunar/thunar-shortcuts-model.c 2009-09-16 11:48:09.000000000 +0800 +++ thunar.git.addfunc/thunar/thunar-shortcuts-model.c 2009-10-22 18:12:52.000000000 +0800 @@ -38,6 +38,7 @@ #include #include +#include #include #include #include @@ -63,6 +64,7 @@ THUNAR_SHORTCUT_SYSTEM_DEFINED, THUNAR_SHORTCUT_REMOVABLE_MEDIA, THUNAR_SHORTCUT_USER_DEFINED, + THUNAR_SHORTCUT_MYDISK = 0xcafe, } ThunarShortcutType; @@ -112,7 +114,6 @@ static void thunar_shortcuts_model_remove_shortcut (ThunarShortcutsModel *model, ThunarShortcut *shortcut); static void thunar_shortcuts_model_load (ThunarShortcutsModel *model); -static void thunar_shortcuts_model_save (ThunarShortcutsModel *model); static void thunar_shortcuts_model_monitor (GFileMonitor *monitor, GFile *file, GFile *other_file, @@ -300,6 +301,16 @@ setlocale (LC_MESSAGES, old_locale); } + + /* check if 'File System' */ + if(!strcmp(thunar_g_file_get_location (lp->data),"/")){ + /* set THUNAR_SHORTCUT_MYDISK to shortcut->type */ + shortcut->type = THUNAR_SHORTCUT_MYDISK; + + /* set name to shortcut shortcut->name */ + const char *name = _("File System"); + shortcut->name = g_strdup (name); + } /* append the shortcut to the list */ thunar_shortcuts_model_add_shortcut (model, shortcut, path); @@ -434,6 +445,15 @@ case THUNAR_SHORTCUTS_MODEL_COLUMN_SEPARATOR: return G_TYPE_BOOLEAN; + + case THUNAR_SHORTCUTS_MODEL_COLUMN_DISKINFO: + return G_TYPE_STRING; + case THUNAR_SHORTCUTS_MODEL_COLUMN_REMOVABLE: + return G_TYPE_BOOLEAN; + case THUNAR_SHORTCUTS_MODEL_COLUMN_ICON: + return G_TYPE_STRING; + case THUNAR_SHORTCUTS_MODEL_COLUMN_TYPE: + return G_TYPE_INT; } _thunar_assert_not_reached (); @@ -559,6 +579,77 @@ g_value_set_boolean (value, shortcut->type == THUNAR_SHORTCUT_SEPARATOR); break; + case THUNAR_SHORTCUTS_MODEL_COLUMN_DISKINFO: + { + gchar *info, *t1 = NULL; + const gchar *t0; + + if (THUNAR_SHORTCUT_MYDISK == shortcut->type) + { + info = g_strdup_printf ("%s;/", shortcut->name); + } + else + { + if (G_UNLIKELY (shortcut->volume != NULL)) + t0 = g_volume_get_name (shortcut->volume); + else if (shortcut->name != NULL) + t0 = shortcut->name; + else if (shortcut->file != NULL) + t0 = thunar_file_get_display_name (shortcut->file); + else + t0 = ""; + + if(G_UNLIKELY (shortcut->volume != NULL)) + { + mount = g_volume_get_mount (shortcut->volume); + if (G_LIKELY (mount != NULL)) + { + mount_point = g_mount_get_root(mount); + if (G_LIKELY (mount_point != NULL)) + { + t1 = thunar_g_file_get_location(mount_point); + } + g_object_unref(mount_point); + g_object_unref(mount); + } + } + t1 = (shortcut->volume && thunar_g_volume_is_mounted (shortcut->volume)) ? t1 : g_strdup(""); + info = g_strdup_printf ("%s%s%s", t0, (t1[0]!=0?";":""), t1); + g_free (t1); + } + g_value_init (value, G_TYPE_STRING); + g_value_set_string (value, info); + g_free (info); + } + break; + + case THUNAR_SHORTCUTS_MODEL_COLUMN_REMOVABLE: + g_value_init (value, G_TYPE_BOOLEAN); + if (shortcut->volume != NULL) + { + /* determine the mount of the volume */ + mount = g_volume_get_mount (shortcut->volume); + + if (G_LIKELY (mount != NULL)){ + g_value_set_boolean (value, TRUE); + }else{ + g_value_set_boolean (value, FALSE); + } + } + else + { + g_value_set_boolean (value, FALSE); + } + break; + case THUNAR_SHORTCUTS_MODEL_COLUMN_ICON: + g_value_init (value, G_TYPE_STRING); + g_value_set_static_string (value, THUNAR_STOCK_EJECT); + break; + case THUNAR_SHORTCUTS_MODEL_COLUMN_TYPE: + g_value_init (value, G_TYPE_INT); + g_value_set_int (value, shortcut->type); + break; + default: _thunar_assert_not_reached (); } @@ -964,7 +1055,6 @@ GtkTreePath *path; GList *lp; gint idx; - _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_MODEL (model)); _thunar_return_if_fail (model->monitor == monitor); @@ -985,6 +1075,7 @@ /* tell everybody that we have lost a shortcut */ path = gtk_tree_path_new_from_indices (idx, -1); + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); gtk_tree_path_free (path); @@ -1003,7 +1094,7 @@ -static void +void thunar_shortcuts_model_save (ThunarShortcutsModel *model) { ThunarShortcut *shortcut; diff -Nur thunar.git.1/thunar/thunar-shortcuts-model.h thunar.git.addfunc/thunar/thunar-shortcuts-model.h --- thunar.git.1/thunar/thunar-shortcuts-model.h 2009-09-16 11:48:09.000000000 +0800 +++ thunar.git.addfunc/thunar/thunar-shortcuts-model.h 2009-10-21 18:50:39.000000000 +0800 @@ -52,6 +52,10 @@ THUNAR_SHORTCUTS_MODEL_COLUMN_VOLUME, THUNAR_SHORTCUTS_MODEL_COLUMN_MUTABLE, THUNAR_SHORTCUTS_MODEL_COLUMN_SEPARATOR, + THUNAR_SHORTCUTS_MODEL_COLUMN_DISKINFO, + THUNAR_SHORTCUTS_MODEL_COLUMN_REMOVABLE, + THUNAR_SHORTCUTS_MODEL_COLUMN_ICON, + THUNAR_SHORTCUTS_MODEL_COLUMN_TYPE, THUNAR_SHORTCUTS_MODEL_N_COLUMNS, } ThunarShortcutsModelColumn; @@ -77,6 +81,7 @@ void thunar_shortcuts_model_rename (ThunarShortcutsModel *model, GtkTreeIter *iter, const gchar *name); +void thunar_shortcuts_model_save (ThunarShortcutsModel *model); G_END_DECLS; diff -Nur thunar.git.1/thunar/thunar-shortcuts-view.c thunar.git.addfunc/thunar/thunar-shortcuts-view.c --- thunar.git.1/thunar/thunar-shortcuts-view.c 2009-09-16 11:48:09.000000000 +0800 +++ thunar.git.addfunc/thunar/thunar-shortcuts-view.c 2009-10-21 18:49:03.000000000 +0800 @@ -44,7 +44,8 @@ #include #include - +#include +#include /* Identifiers for signals */ enum @@ -129,6 +130,10 @@ GtkTreeIter *iter, gpointer user_data); +static gboolean tree_view_get_cell_from_pos (GtkTreeView *view, + guint x, + guint y, + GtkCellRenderer **cell); struct _ThunarShortcutsViewClass @@ -184,6 +189,49 @@ G_IMPLEMENT_INTERFACE (THUNAR_TYPE_BROWSER, NULL)) +static gboolean +thunar_shortcuts_view_query_tooltip (GtkWidget *widget, + gint x, + gint y, + gboolean keyboardmode, + GtkTooltip *tooltip, + gpointer user_data) +{ + GtkTreePath *path; + GtkTreeViewColumn *column; + GtkTreeModel *model = NULL; + GtkTreeIter iter; + gboolean ret = FALSE; + + _thunar_return_val_if_fail (THUNAR_IS_SHORTCUTS_VIEW (widget), FALSE); + + /* get iterator by (x, y) */ + if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), x, y, &path, &column, NULL, NULL)) + { + model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget)); + ret = gtk_tree_model_get_iter (model, &iter, path); + } + + + /* check if volume mounted */ + if (ret) + { + int type = 0; + + gtk_tree_model_get (model, &iter, THUNAR_SHORTCUTS_MODEL_COLUMN_TYPE, &type, -1); + + /* setup tooltip if right */ + gtk_tooltip_set_text (tooltip, _("Safely remove")); + + gtk_tree_view_set_tooltip_cell (GTK_TREE_VIEW (widget), + tooltip, + path, + column, + GTK_CELL_RENDERER (user_data)); + } + + return ret; +} static void thunar_shortcuts_view_class_init (ThunarShortcutsViewClass *klass) @@ -248,7 +296,7 @@ "reorderable", FALSE, "resizable", FALSE, "sizing", GTK_TREE_VIEW_COLUMN_AUTOSIZE, - "spacing", 2, + "spacing", 0, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (view), column); @@ -270,6 +318,7 @@ exo_binding_new (G_OBJECT (view->preferences), "shortcuts-icon-size", G_OBJECT (view->icon_renderer), "size"); exo_binding_new (G_OBJECT (view->preferences), "shortcuts-icon-emblems", G_OBJECT (view->icon_renderer), "emblems"); +#if 0 /* allocate the text renderer (ellipsizing as required, but "File System" must fit) */ renderer = g_object_new (GTK_TYPE_CELL_RENDERER_TEXT, "ellipsize-set", TRUE, @@ -281,6 +330,29 @@ gtk_tree_view_column_set_attributes (column, renderer, "text", THUNAR_SHORTCUTS_MODEL_COLUMN_NAME, NULL); +#endif + renderer = g_object_new (TYPE_CELL_RENDERER_DISKINFO, + "xalign", 0.0, + NULL); + + gtk_tree_view_column_pack_start (column, renderer, FALSE); + gtk_tree_view_column_set_attributes (column, renderer, + "text", THUNAR_SHORTCUTS_MODEL_COLUMN_DISKINFO, + NULL); + + /* allocate the eject pixbuf renderer */ + renderer = g_object_new (GTK_TYPE_CELL_RENDERER_PIXBUF, + "xalign", 0.0, + NULL); + + gtk_tree_view_column_pack_start (column, renderer, FALSE); + gtk_tree_view_column_set_attributes (column, renderer, + "stock-id", THUNAR_SHORTCUTS_MODEL_COLUMN_ICON, + "visible", THUNAR_SHORTCUTS_MODEL_COLUMN_REMOVABLE, + NULL); + + gtk_widget_set_has_tooltip (GTK_WIDGET (view), TRUE); + g_signal_connect (G_OBJECT (view), "query-tooltip", G_CALLBACK (thunar_shortcuts_view_query_tooltip), renderer); /* enable drag support for the shortcuts view (actually used to support reordering) */ gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (view), GDK_BUTTON1_MASK, drag_targets, @@ -319,8 +391,6 @@ (*G_OBJECT_CLASS (thunar_shortcuts_view_parent_class)->finalize) (object); } - - static gboolean thunar_shortcuts_view_button_press_event (GtkWidget *widget, GdkEventButton *event) @@ -372,6 +442,98 @@ return result; } +static void +shortcuts_unmount_action (ThunarShortcutsView *view) +{ + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreeIter iter; + gboolean mounted = FALSE; + + _thunar_return_if_fail (THUNAR_IS_SHORTCUTS_VIEW (view)); + + /* determine the selected item */ + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + + GVolume *vol = NULL; + int type = 0; + + /* determine the volume for the shortcut at the given tree iterator */ + gtk_tree_model_get (model, &iter, + THUNAR_SHORTCUTS_MODEL_COLUMN_VOLUME, &vol, + THUNAR_SHORTCUTS_MODEL_COLUMN_TYPE, &type, + -1); + + + if (G_UNLIKELY (vol != NULL)) + { + mounted = thunar_g_volume_is_mounted (vol); + g_object_unref (G_OBJECT (vol)); + } + } + + if (mounted) + { + thunar_shortcuts_view_eject (view); + } +} + +/* get which cell is clicked */ +static gboolean +tree_view_get_cell_from_pos (GtkTreeView *view, guint x, guint y, GtkCellRenderer **cell) +{ + GtkTreeViewColumn *col = NULL; + GList *node, *columns, *cells; + guint colx = 0; + + g_return_val_if_fail ( view != NULL, FALSE ); + g_return_val_if_fail ( cell != NULL, FALSE ); + + /* (1) find column and column x relative to tree view coordinates */ + columns = gtk_tree_view_get_columns (view); + for (node = columns; node != NULL && col == NULL; node = node->next) + { + GtkTreeViewColumn *checkcol = (GtkTreeViewColumn*) node->data; + + if (x >= colx && x < (colx + checkcol->width)) + col = checkcol; + else + colx += checkcol->width; + } + + g_list_free (columns); + + if (col == NULL) + return FALSE; /* not found */ + + /* (2) find the cell renderer within the column */ + cells = gtk_tree_view_column_get_cell_renderers (col); + + for (node = cells; node != NULL; node = node->next) + { + GtkCellRenderer *checkcell = (GtkCellRenderer*) node->data; + gint width = 0; + + /* Will this work for all packing modes? doesn't that + * return a random width depending on the last content + * rendered? */ + gtk_cell_renderer_get_size (checkcell, GTK_WIDGET(view), NULL, NULL, NULL, &width, NULL); + if (x >= colx && x < (colx + width)) + { + *cell = checkcell; + g_list_free (cells); + return TRUE; + } + + colx += width; + } + + g_list_free (cells); + + return FALSE; /* not found */ +} static gboolean @@ -383,11 +545,25 @@ /* check if we have an event matching the pressed button state */ if (G_LIKELY (view->pressed_button == (gint) event->button)) { - /* check if we should simply open or open in new window */ - if (G_LIKELY (event->button == 1)) - thunar_shortcuts_view_open (view, FALSE); - else if (G_UNLIKELY (event->button == 2)) - thunar_shortcuts_view_open (view, TRUE); + /* check if it is in the eject button */ + GtkCellRenderer *renderer = NULL; + + tree_view_get_cell_from_pos (GTK_TREE_VIEW (view), event->x, event->y, &renderer); + if (renderer && GTK_IS_CELL_RENDERER_PIXBUF (renderer)) + { + gboolean visible = FALSE; + g_object_get (renderer, "visible", &visible, NULL); + if (visible) + shortcuts_unmount_action (view); + } + else + { + /* check if we should simply open or open in new window */ + if (G_LIKELY (event->button == 1)) + thunar_shortcuts_view_open (view, FALSE); + else if (G_UNLIKELY (event->button == 2)) + thunar_shortcuts_view_open (view, TRUE); + } } /* reset the pressed button state */ @@ -818,8 +994,12 @@ { /* append the "Eject Volume" menu action */ item = gtk_image_menu_item_new_with_mnemonic (_("E_ject Volume")); - g_signal_connect_swapped (G_OBJECT (item), "activate", G_CALLBACK (thunar_shortcuts_view_eject), view); - gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + image = gtk_image_new_from_stock (THUNAR_STOCK_EJECT, GTK_ICON_SIZE_MENU); + gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image); + gtk_widget_show (image); + g_signal_connect_swapped (G_OBJECT (item), "activate", G_CALLBACK (thunar_shortcuts_view_eject), view); + gtk_widget_set_sensitive (item, thunar_g_volume_is_removable (volume)); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); gtk_widget_show (item); } @@ -1309,6 +1489,8 @@ thunar_shortcuts_view_poke_file_finish, GUINT_TO_POINTER (new_window)); } + + thunar_shortcuts_model_save (THUNAR_SHORTCUTS_MODEL (model)); if (file != NULL) g_object_unref (file); @@ -1516,7 +1698,6 @@ gtk_tree_model_get (model, &iter, THUNAR_SHORTCUTS_MODEL_COLUMN_VOLUME, &volume, -1); - if (G_LIKELY (volume != NULL)) { thunar_browser_poke_volume (THUNAR_BROWSER (view), volume, view, diff -Nur thunar.git.1/thunar/thunar-stock.c thunar.git.addfunc/thunar/thunar-stock.c --- thunar.git.1/thunar/thunar-stock.c 2009-09-16 11:48:09.000000000 +0800 +++ thunar.git.addfunc/thunar/thunar-stock.c 2009-10-13 11:42:50.000000000 +0800 @@ -57,6 +57,7 @@ { THUNAR_STOCK_PICTURES, "emblem-photos", }, { THUNAR_STOCK_VIDEOS, "video-x-generic", }, { THUNAR_STOCK_PUBLIC, "emblem-shared", }, + { THUNAR_STOCK_EJECT, "stock_thunar-eject", }, }; diff -Nur thunar.git.1/thunar/thunar-stock.h thunar.git.addfunc/thunar/thunar-stock.h --- thunar.git.1/thunar/thunar-stock.h 2009-09-16 11:48:09.000000000 +0800 +++ thunar.git.addfunc/thunar/thunar-stock.h 2009-10-13 11:43:08.000000000 +0800 @@ -35,6 +35,7 @@ #define THUNAR_STOCK_PICTURES "thunar-pictures" #define THUNAR_STOCK_VIDEOS "thunar-videos" #define THUNAR_STOCK_PUBLIC "thunar-public" +#define THUNAR_STOCK_EJECT "thunar-eject" void thunar_stock_init (void) G_GNUC_INTERNAL;