diff --git a/ChangeLog b/ChangeLog index 2eefacfd6049021f12a1303e9d1081ddcbf36805..51d4cca9d92efe4153d7c3b10ca9a8faa901de4a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,31 @@ +2005-06-11 Benedikt Meurer <benny@xfce.org> + + * thunar-vfs/thunar-vfs-uri.{c,h}: Add functions to ease the handling + of URI lists, specifically to automatically parse and generate + string representations of URI lists that conform to the + text/uri-list mime type. + * thunar/thunar-location-buttons.c + (thunar_location_buttons_drag_data_get): Use new ThunarVfsURI list + handling functions instead. + * thunar/thunar-favourites-model.c + (thunar_favourites_model_file_destroy): Handle the case where a + directory referenced by a favourite disappears from the backend media. + * thunar/thunar-favourites-model.c + (thunar_favourites_model_file_changed): Remove a given favourite if + the system notices that the favourite's file no longer refers to a + directory. + * thunar/thunar-favourites-model.c(thunar_favourites_model_init): Do + not re-add favourites initially, that do not refer to a directory. + * thunar/thunar-favourites-model.{c,h}: Add a new method + thunar_favourites_model_add(), which is used by the + ThunarFavouritesView to add new favourites to the list and + automatically sync the changes with the Gtk+ bookmarks list. + * thunar/thunar-favourites-view.c: Handle "text/uri-list" drops, + adding new favourites as appropriate. Add note, that the initial + idea is based on the GtkFileChooser written by Red Hat for Gtk+. + * TODO: Remove the 'text/uri-list'-handling for ThunarFavouritesView. + Add item concerning the Trash in the favourites list. + 2005-06-10 Benedikt Meurer <benny@xfce.org> * thunar/thunar-favourites-model.{c,h}, diff --git a/TODO b/TODO index 0e961404b24eab2b7eebe1cda081890eec5868cc..f7f767fc04a316ab6cd3a57537b3e0948b52bf89 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,12 @@ Important for Thunar 1.0 ======================== - - Handle text/uri-list drops on the ThunarFavouritesView. + - ThunarFavouritesModel should include the 'Trash' in the default system + favourites list, given the user the ability to atleast empty the trash + by right-clicking on the favourite and choosing 'Empty trash bin' from + the context menu. An optional, yet tricky feature, would be to allow + dropping files to the trash bin in the favourites pane (not for 1.0 I'd + say). - ThunarFavouritesModel should watch the .gtk-bookmarks file for changes and reload it on-demand (take care of not reloading if the change was caused by diff --git a/thunar-vfs/thunar-vfs-uri.c b/thunar-vfs/thunar-vfs-uri.c index f472b194e4d65be71e8e955071442af602ce1dc8..a8a6f0a7189979c9a39269b1ee65a802b11df848 100644 --- a/thunar-vfs/thunar-vfs-uri.c +++ b/thunar-vfs/thunar-vfs-uri.c @@ -105,12 +105,18 @@ thunar_vfs_uri_new (const gchar *identifier, { ThunarVfsURI *uri; const gchar *p; + gchar *path; g_return_val_if_fail (identifier != NULL, NULL); + /* try to parse the path */ + path = g_filename_from_uri (identifier, NULL, error); + if (G_UNLIKELY (path == NULL)) + return NULL; + /* allocate the URI instance */ uri = g_object_new (THUNAR_VFS_TYPE_URI, NULL); - uri->path = g_filename_from_uri (identifier, NULL, error); + uri->path = path; /* determine the basename of the path */ for (p = uri->name = uri->path; *p != '\0'; ++p) @@ -273,7 +279,8 @@ thunar_vfs_uri_get_path (ThunarVfsURI *uri) * Returns the #ThunarVfsURI object that refers to the parent * folder of @uri or %NULL if @uri has no parent. * - * Return value: + * Return value: the #ThunarVfsURI object referring to the parent folder + * or %NULL. **/ ThunarVfsURI* thunar_vfs_uri_parent (ThunarVfsURI *uri) @@ -305,7 +312,7 @@ thunar_vfs_uri_parent (ThunarVfsURI *uri) * @uri : an #ThunarVfsURI instance. * @name : the relative name. * - * Return value: + * Return value: **/ ThunarVfsURI* thunar_vfs_uri_relative (ThunarVfsURI *uri, @@ -429,4 +436,155 @@ thunar_vfs_uri_equal (gconstpointer a, +/** + * thunar_vfs_uri_list_from_string: + * @string : string representation of an URI list. + * @error : return location for errors. + * + * Splits an URI list conforming to the text/uri-list + * mime type defined in RFC 2483 into individual URIs, + * discarding any comments and whitespace. + * + * If all URIs were successfully parsed into #ThunarVfsURI + * objects, the list of parsed URIs will be returned, and + * you'll need to call #thunar_vfs_uri_list_free() to + * release the list resources. Else if the parsing fails + * at some point, %NULL will be returned and @error will + * be set to describe the cause. + * + * Note, that if @string contains no URIs, this function + * will also return %NULL, but @error won't be set. So + * take care when checking for an error condition! + * + * Return value: the list of #ThunarVfsURI's or %NULL. + **/ +GList* +thunar_vfs_uri_list_from_string (const gchar *string, + GError **error) +{ + ThunarVfsURI *uri; + const gchar *s; + const gchar *t; + GList *uri_list = NULL; + gchar *identifier; + + g_return_val_if_fail (string != NULL, NULL); + + for (s = string; s != NULL; ) + { + if (*s != '#') + { + while (g_ascii_isspace (*s)) + ++s; + + for (t = s; *t != '\0' && *t != '\n' && *t != '\r'; ++t) + ; + + if (t > s) + { + for (t--; t > s && g_ascii_isspace (*t); t--) + ; + + if (t > s) + { + /* try to parse the URI */ + identifier = g_strndup (s, t - s + 1); + uri = thunar_vfs_uri_new (identifier, error); + g_free (identifier); + + /* check if we succeed */ + if (G_UNLIKELY (uri == NULL)) + { + thunar_vfs_uri_list_free (uri_list); + return NULL; + } + else + { + /* append the newly parsed uri */ + uri_list = g_list_append (uri_list, uri); + } + } + } + } + + for (; *s != '\0' && *s != '\n'; ++s) + ; + + if (*s++ == '\0') + break; + } + + return uri_list; +} + + + +/** + * thunar_vfs_uri_list_to_string: + * @uri_list : a list of #ThunarVfsURI<!---->s. + * + * Free the returned value using #g_free() when you + * are done with it. + * + * Return value: the string representation of @uri_list conforming to the + * text/uri-list mime type defined in RFC 2483. + **/ +gchar* +thunar_vfs_uri_list_to_string (GList *uri_list) +{ + gchar *string_list; + gchar *uri_string; + gchar *new_list; + GList *lp; + + string_list = g_strdup (""); + + /* This way of building up the string representation is pretty + * slow and can lead to serious heap fragmentation if called + * too often. But this doesn't matter, as this function is not + * called very often (in fact it should only be used in Thunar + * when a file drag is started from within Thunar). If you can + * think of any easy way to avoid calling malloc that often, + * let me know (of course without depending too much on the + * exact internal representation and the type of URIs). Else + * don't waste any time or thought on this. + */ + for (lp = uri_list; lp != NULL; lp = lp->next) + { + g_assert (THUNAR_VFS_IS_URI (lp->data)); + + uri_string = thunar_vfs_uri_to_string (THUNAR_VFS_URI (lp->data)); + new_list = g_strconcat (string_list, uri_string, "\r\n", NULL); + g_free (string_list); + g_free (uri_string); + string_list = new_list; + } + + return string_list; +} + + + +/** + * thunar_vfs_uri_list_free: + * @uri_list : a list of #ThunarVfsURI<!---->s. + * + * Frees the #ThunarVfsURI<!---->s contained in @uri_list and + * the @uri_list itself. + **/ +void +thunar_vfs_uri_list_free (GList *uri_list) +{ + GList *lp; + + for (lp = uri_list; lp != NULL; lp = lp->next) + { + g_assert (THUNAR_VFS_IS_URI (lp->data)); + g_object_unref (G_OBJECT (lp->data)); + } + + g_list_free (uri_list); +} + + diff --git a/thunar-vfs/thunar-vfs-uri.h b/thunar-vfs/thunar-vfs-uri.h index 9e5337977248f812fa1580f79b2d52872304aeaf..031879f7a2069ab37bbc3067ed878afa61826565 100644 --- a/thunar-vfs/thunar-vfs-uri.h +++ b/thunar-vfs/thunar-vfs-uri.h @@ -59,6 +59,16 @@ guint thunar_vfs_uri_hash (gconstpointer uri); gboolean thunar_vfs_uri_equal (gconstpointer a, gconstpointer b); +GList *thunar_vfs_uri_list_from_string (const gchar *string, + GError **error); +gchar *thunar_vfs_uri_list_to_string (GList *uri_list); +void thunar_vfs_uri_list_free (GList *uri_list); + +#define thunar_vfs_uri_list_append(uri_list, uri) \ + g_list_append ((uri_list), g_object_ref (G_OBJECT ((uri)))) +#define thunar_vfs_uri_list_prepend(uri_list, uri) \ + g_list_prepend ((uri_list), g_object_ref (G_OBJECT ((uri)))) + G_END_DECLS; #endif /* !__THUNAR_VFS_URI_H__ */ diff --git a/thunar/thunar-favourites-model.c b/thunar/thunar-favourites-model.c index 01e8d7f7c0d9b6e475af9af00fde44b8e457b374..232c2979c9c84dec67f3fa41c937cde89751fd13 100644 --- a/thunar/thunar-favourites-model.c +++ b/thunar/thunar-favourites-model.c @@ -170,6 +170,13 @@ thunar_favourites_model_init (ThunarFavouritesModel *model) if (G_UNLIKELY (file == NULL)) continue; + /* make sure the file refers to a directory */ + if (G_UNLIKELY (!thunar_file_is_directory (file))) + { + g_object_unref (G_OBJECT (file)); + continue; + } + /* create the favourite entry */ favourite = g_new0 (ThunarFavourite, 1); favourite->file = file; @@ -669,6 +676,16 @@ thunar_favourites_model_file_changed (ThunarFile *file, g_return_if_fail (THUNAR_IS_FILE (file)); g_return_if_fail (THUNAR_IS_FAVOURITES_MODEL (model)); + + /* check if the file still refers to a directory, else we cannot keep + * it on the favourites list, and so we'll treat it like the file + * was destroyed (and thereby removed) + */ + if (G_UNLIKELY (!thunar_file_is_directory (file))) + { + thunar_favourites_model_file_destroy (file, model); + return; + } for (favourite = model->favourites, n = 0; favourite != NULL; favourite = favourite->next, ++n) if (favourite->file == file) @@ -688,10 +705,52 @@ static void thunar_favourites_model_file_destroy (ThunarFile *file, ThunarFavouritesModel *model) { + ThunarFavourite *favourite; + GtkTreePath *path; + gint n; + g_return_if_fail (THUNAR_IS_FILE (file)); g_return_if_fail (THUNAR_IS_FAVOURITES_MODEL (model)); - // TODO: Implement this function. + /* lookup the favourite matching the file */ + for (favourite = model->favourites, n = 0; favourite != NULL; favourite = favourite->next, ++n) + if (favourite->file == file) + break; + + g_assert (favourite != NULL); + g_assert (n < model->n_favourites); + g_assert (THUNAR_IS_FILE (favourite->file)); + + /* unlink the favourite from the model */ + --model->n_favourites; + if (favourite == model->favourites) + { + favourite->next->prev = NULL; + model->favourites = favourite->next; + } + else + { + if (favourite->next != NULL) + favourite->next->prev = favourite->prev; + favourite->prev->next = favourite->next; + } + + /* tell everybody that we have lost a favourite */ + path = gtk_tree_path_new_from_indices (n, -1); + gtk_tree_model_row_deleted (GTK_TREE_MODEL (model), path); + gtk_tree_path_free (path); + + /* disconnect us from the favourite's file */ + g_signal_handlers_disconnect_matched (G_OBJECT (favourite->file), + G_SIGNAL_MATCH_DATA, 0, + 0, NULL, NULL, model); + g_object_unref (G_OBJECT (favourite->file)); + + /* the favourites list was changed, so write the gtk bookmarks file */ + thunar_favourites_model_save (model); + + /* free the favourite data */ + g_free (favourite); } @@ -822,6 +881,95 @@ thunar_favourites_model_drop_possible (ThunarFavouritesModel *model, +/** + * thunar_favourites_model_add: + * @model : a #ThunarFavouritesModel. + * @dst_path : the destination path. + * @file : the #ThunarFile that should be added to the favourites list. + * + * Adds the favourite @file to the @model at @dst_path, unless @file is + * already present in @model in which case no action is performed. + **/ +void +thunar_favourites_model_add (ThunarFavouritesModel *model, + GtkTreePath *dst_path, + ThunarFile *file) +{ + ThunarFavourite *favourite; + ThunarFavourite *current; + GtkTreeIter iter; + gint index; + + g_return_if_fail (THUNAR_IS_FAVOURITES_MODEL (model)); + g_return_if_fail (gtk_tree_path_get_depth (dst_path) > 0); + g_return_if_fail (gtk_tree_path_get_indices (dst_path)[0] >= 0); + g_return_if_fail (gtk_tree_path_get_indices (dst_path)[0] <= model->n_favourites); + g_return_if_fail (THUNAR_IS_FILE (file)); + + /* verify that the file is not already in use as favourite */ + for (favourite = model->favourites; favourite != NULL; favourite = favourite->next) + if (G_UNLIKELY (favourite->file == file)) + return; + + /* create the new favourite that will be inserted */ + favourite = g_new0 (ThunarFavourite, 1); + favourite->file = file; + g_object_ref (G_OBJECT (file)); + + /* we want to stay informed about changes to the file */ + g_signal_connect (G_OBJECT (file), "changed", + G_CALLBACK (thunar_favourites_model_file_changed), model); + g_signal_connect (G_OBJECT (file), "destroy", + G_CALLBACK (thunar_favourites_model_file_destroy), model); + + /* check if this is the first favourite to insert (shouldn't happen normally) */ + if (G_UNLIKELY (model->favourites == NULL)) + model->favourites = favourite; + else + { + index = gtk_tree_path_get_indices (dst_path)[0]; + if (index == 0) + { + /* the new favourite should be prepended to the existing list */ + favourite->next = model->favourites; + model->favourites->prev = favourite; + model->favourites = favourite; + } + else if (index == model->n_favourites) + { + /* the new favourite should be appended to the existing list */ + for (current = model->favourites; current->next != NULL; current = current->next) + ; + + favourite->prev = current; + current->next = favourite; + } + else + { + /* inserting somewhere into the existing list */ + for (current = model->favourites; index-- > 0; current = current->next) + ; + + favourite->next = current; + favourite->prev = current->prev; + current->prev->next = favourite; + current->prev = favourite; + } + } + + /* we've just inserted a new item */ + ++model->n_favourites; + + /* tell everybody that we have a new favourite */ + gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, dst_path); + gtk_tree_model_row_inserted (GTK_TREE_MODEL (model), dst_path, &iter); + + /* the favourites list was changed, so write the gtk bookmarks file */ + thunar_favourites_model_save (model); +} + + + /** * thunar_favourites_model_move: * @model : a #ThunarFavouritesModel. diff --git a/thunar/thunar-favourites-model.h b/thunar/thunar-favourites-model.h index 285fd26c7d0fef2f579c69521a12337ac5177016..a56dc3130d58d183cb3cae59f9807005b05e27f8 100644 --- a/thunar/thunar-favourites-model.h +++ b/thunar/thunar-favourites-model.h @@ -61,6 +61,9 @@ ThunarFile *thunar_favourites_model_file_for_iter (ThunarFavouritesM gboolean thunar_favourites_model_drop_possible (ThunarFavouritesModel *model, GtkTreePath *path); +void thunar_favourites_model_add (ThunarFavouritesModel *model, + GtkTreePath *dst_path, + ThunarFile *file); void thunar_favourites_model_move (ThunarFavouritesModel *model, GtkTreePath *src_path, GtkTreePath *dst_path); diff --git a/thunar/thunar-favourites-view.c b/thunar/thunar-favourites-view.c index d1e0991cc8b24ce2b42bd7237a6a3281782ef095..020bfc294f0a0231edbc3a1fa9aa267a22adfe29 100644 --- a/thunar/thunar-favourites-view.c +++ b/thunar/thunar-favourites-view.c @@ -15,6 +15,9 @@ * 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 + * + * Inspired by the shortcuts list as found in the GtkFileChooser, which was + * developed for Gtk+ by Red Hat, Inc. */ #ifdef HAVE_CONFIG_H @@ -67,6 +70,9 @@ static gboolean thunar_favourites_view_drag_motion (GtkWidget static GtkTreePath *thunar_favourites_view_compute_drop_position (ThunarFavouritesView *view, gint x, gint y); +static void thunar_favourites_view_drop_uri_list (ThunarFavouritesView *view, + const gchar *uri_list, + GtkTreePath *dst_path); #if GTK_CHECK_VERSION(2,6,0) static gboolean thunar_favourites_view_separator_func (GtkTreeModel *model, GtkTreeIter *iter, @@ -238,7 +244,7 @@ thunar_favourites_view_drag_data_received (GtkWidget *widget, if (selection_data->target == gdk_atom_intern ("text/uri-list", FALSE)) { - g_error ("text/uri-list not handled yet"); + thunar_favourites_view_drop_uri_list (view, selection_data->data, dst_path); } else if (selection_data->target == gdk_atom_intern ("GTK_TREE_MODEL_ROW", FALSE)) { @@ -368,6 +374,71 @@ thunar_favourites_view_compute_drop_position (ThunarFavouritesView *view, +static void +thunar_favourites_view_drop_uri_list (ThunarFavouritesView *view, + const gchar *uri_list, + GtkTreePath *dst_path) +{ + GtkTreeModel *model; + ThunarFile *file; + GtkWidget *toplevel; + GtkWidget *dialog; + GError *error = NULL; + gchar *uri_string; + GList *uris; + GList *lp; + + uris = thunar_vfs_uri_list_from_string (uri_list, &error); + if (G_LIKELY (error == NULL)) + { + /* process the URIs one-by-one and stop on error */ + model = gtk_tree_view_get_model (GTK_TREE_VIEW (view)); + for (lp = uris; lp != NULL; lp = lp->next) + { + file = thunar_file_get_for_uri (lp->data, &error); + if (G_UNLIKELY (file == NULL)) + break; + + /* make sure, that only directories gets added to the favourites list */ + if (G_UNLIKELY (!thunar_file_is_directory (file))) + { + uri_string = thunar_vfs_uri_to_string (lp->data); + g_set_error (&error, G_FILE_ERROR, G_FILE_ERROR_NOTDIR, + "The URI '%s' does not refer to a directory", + uri_string); + g_object_unref (G_OBJECT (file)); + g_free (uri_string); + break; + } + + thunar_favourites_model_add (THUNAR_FAVOURITES_MODEL (model), dst_path, file); + g_object_unref (G_OBJECT (file)); + gtk_tree_path_next (dst_path); + } + + thunar_vfs_uri_list_free (uris); + } + + if (G_UNLIKELY (error != NULL)) + { + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (view)); + dialog = gtk_message_dialog_new_with_markup (GTK_WINDOW (toplevel), + GTK_DIALOG_DESTROY_WITH_PARENT + | GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, + "<span weight=\"bold\" size=" + "\"larger\">%s</span>\n\n%s", + _("Could not add favourite"), + error->message); + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + g_error_free (error); + } +} + + + #if GTK_CHECK_VERSION(2,6,0) static gboolean thunar_favourites_view_separator_func (GtkTreeModel *model, diff --git a/thunar/thunar-location-buttons.c b/thunar/thunar-location-buttons.c index df06f8660df2febcc58ac75ee570afa7730f4a6d..6fb84104561fe2b3b255ba483f23bc2683c4540f 100644 --- a/thunar/thunar-location-buttons.c +++ b/thunar/thunar-location-buttons.c @@ -1009,7 +1009,7 @@ thunar_location_buttons_drag_data_get (GtkWidget *button, { ThunarVfsURI *uri; ThunarFile *file; - gchar *uri_list; + GList *uri_list = NULL; gchar *uri_string; g_return_if_fail (GTK_IS_WIDGET (button)); @@ -1019,17 +1019,17 @@ thunar_location_buttons_drag_data_get (GtkWidget *button, file = g_object_get_qdata (G_OBJECT (button), thunar_file_quark); uri = thunar_file_get_uri (file); - /* transform the uri into an uri list (using DOS line endings!) */ - uri_string = thunar_vfs_uri_to_string (uri); - uri_list = g_strconcat (uri_string, "\r\n", NULL); + /* transform the uri into an uri list string */ + uri_list = thunar_vfs_uri_list_prepend (uri_list, uri); + uri_string = thunar_vfs_uri_list_to_string (uri_list); + thunar_vfs_uri_list_free (uri_list); /* set the uri list for the drag selection */ gtk_selection_data_set (selection_data, selection_data->target, - 8, uri_list, strlen (uri_list)); + 8, uri_string, strlen (uri_string)); /* cleanup */ g_free (uri_string); - g_free (uri_list); }