/* vi:set et ai sw=2 sts=2 ts=2: */ /*- * Copyright (c) 2006-2007 Benedikt Meurer * Copyright (c) 2009-2010 Jannis Pohlmann * * 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., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef HAVE_SYS_TYPES_H #include #endif #ifdef HAVE_SYS_PARAM_H #include #endif #ifdef HAVE_MEMORY_H #include #endif #ifdef HAVE_PWD_H #include #endif #ifdef HAVE_STDLIB_H #include #endif #ifdef HAVE_STRING_H #include #endif #ifdef HAVE_TIME_H #include #endif #ifdef HAVE_UNISTD_H #include #endif #ifdef G_PLATFORM_WIN32 #include #include #endif #include "thunar/thunar-application.h" #include "thunar/thunar-folder.h" #include "thunar/thunar-icon-renderer.h" #include "thunar/thunar-io-scan-directory.h" #include "thunar/thunar-preferences.h" #include "thunar/thunar-private.h" #include "thunar/thunar-renamer-dialog.h" #include "thunar/thunar-text-renderer.h" #include "thunar/thunar-util.h" #include "thunar/thunar-window.h" #include #include #define BORDER_RADIUS 8 enum { THUNAR_CELL_TOP_RIGHT, THUNAR_CELL_BOTTOM_RIGHT, THUNAR_CELL_TOP_LEFT, THUNAR_CELL_BOTTOM_LEFT, }; enum { DRAW_ON_LEFT, DRAW_ON_RIGHT, DRAW_ON_BOTTOM, DRAW_ON_TOP, DRAW_ON_ALL_SIDES, } DrawOnSide; /** * thunar_util_strrchr_offset: * @str: haystack * @offset: pointer offset in @str * @c: search needle * * Return the last occurrence of the character @c in * the string @str starting at @offset. * * There are also Glib functions for this like g_strrstr_len * and g_utf8_strrchr, but these work internally the same * as this function (tho, less efficient). * * Return value: pointer in @str or NULL. **/ static inline gchar * thunar_util_strrchr_offset (const gchar *str, const gchar *offset, gchar c) { const gchar *p; for (p = offset; p > str; p--) if (*p == c) return (gchar *) p; return NULL; } /** * thunar_util_str_get_extension * @filename : an UTF-8 filename * * Returns a pointer to the extension in @filename. * * This is an improved version of g_utf8_strrchr with * improvements to recognize compound extensions like * ".tar.gz" and ".desktop.in.in". * * Return value: pointer to the extension in @filename * or NULL. **/ gchar * thunar_util_str_get_extension (const gchar *filename) { static const gchar *compressed[] = { "gz", "bz2", "lzma", "lrz", "rpm", "lzo", "xz", "z" }; gchar *dot; gchar *ext; guint i; gchar *dot2; gsize len; gboolean is_in; /* check if there is an possible extension part in the name */ dot = strrchr (filename, '.'); if (dot == NULL || dot == filename || dot[1] == '\0') return NULL; /* skip the . */ ext = dot + 1; /* check if this looks like a compression mime-type */ for (i = 0; i < G_N_ELEMENTS (compressed); i++) { if (strcasecmp (ext, compressed[i]) == 0) { /* look for a possible container part (tar, psd, epsf) */ dot2 = thunar_util_strrchr_offset (filename, dot - 1, '.'); if (dot2 != NULL && dot2 != filename) { /* check the 2nd part range, keep it between 2 and 5 chars */ len = dot - dot2 - 1; if (len >= 2 && len <= 5) dot = dot2; } /* that's it for compression types */ return dot; } } /* for coders, .in are quite common, so check for those too * with a max of 3 rounds (2x .in and the possibly final extension) */ if (strcasecmp (ext, "in") == 0) { for (i = 0, is_in = TRUE; is_in && i < 3; i++) { dot2 = thunar_util_strrchr_offset (filename, dot - 1, '.'); /* the extension before .in could be long. check that it's at least 2 chars */ len = dot - dot2 - 1; if (dot2 == NULL || dot2 == filename || len < 2) break; /* continue if another .in was found */ is_in = dot - dot2 == 3 && strncasecmp (dot2, ".in", 3) == 0; dot = dot2; } } return dot; } void thunar_util_load_bookmarks (GFile *bookmarks_file, ThunarBookmarksFunc foreach_func, gpointer user_data) { gchar *bookmarks_path; gchar line[1024]; const gchar *name; gchar *space; FILE *fp; gint row_num = 1; GFile *file; _thunar_return_if_fail (G_IS_FILE (bookmarks_file)); _thunar_return_if_fail (g_file_is_native (bookmarks_file)); _thunar_return_if_fail (foreach_func != NULL); /* determine the path to the GTK+ bookmarks file */ bookmarks_path = g_file_get_path (bookmarks_file); /* append the GTK+ bookmarks (if any) */ fp = fopen (bookmarks_path, "r"); g_free (bookmarks_path); if (G_UNLIKELY (fp == NULL)) { bookmarks_path = g_build_filename (g_get_home_dir (), ".gtk-bookmarks", NULL); fp = fopen (bookmarks_path, "r"); g_free (bookmarks_path); } if (G_LIKELY (fp != NULL)) { while (fgets (line, sizeof (line), fp) != NULL) { /* remove trailing spaces */ g_strchomp (line); /* skip over empty lines */ if (*line == '\0' || *line == ' ') continue; /* check if there is a custom name in the line */ name = NULL; space = strchr (line, ' '); if (space != NULL) { /* break line */ *space++ = '\0'; /* get the custom name */ if (G_LIKELY (*space != '\0')) name = space; } file = g_file_new_for_uri (line); /* callback */ foreach_func (file, name, row_num++, user_data); g_object_unref (G_OBJECT (file)); } fclose (fp); } } /** * thunar_util_expand_filename: * @filename : a local filename. * @working_directory : #GFile of the current working directory. * @error : return location for errors or %NULL. * * Takes a user-typed @filename and expands a tilde at the * beginning of the @filename. It also resolves paths prefixed with * '.' using the current working directory. * * The caller is responsible to free the returned string using * g_free() when no longer needed. * * Return value: the expanded @filename or %NULL on error. **/ gchar * thunar_util_expand_filename (const gchar *filename, GFile *working_directory, GError **error) { struct passwd *passwd; const gchar *replacement; const gchar *remainder; const gchar *slash; gchar *username; gchar *pwd; gchar *variable; gchar *result = NULL; g_return_val_if_fail (filename != NULL, NULL); /* check if we have a valid (non-empty!) filename */ if (G_UNLIKELY (*filename == '\0')) { g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, _("Invalid path")); return NULL; } /* check if we start with a '~' */ if (*filename == '~') { /* examine the remainder of the filename */ remainder = filename + 1; /* if we have only the slash, then we want the home dir */ if (G_UNLIKELY (*remainder == '\0')) return g_strdup (xfce_get_homedir ()); /* lookup the slash */ for (slash = remainder; *slash != '\0' && *slash != G_DIR_SEPARATOR; ++slash) ; /* check if a username was given after the '~' */ if (G_LIKELY (slash == remainder)) { /* replace the tilde with the home dir */ replacement = xfce_get_homedir (); } else { /* lookup the pwd entry for the username */ username = g_strndup (remainder, slash - remainder); passwd = getpwnam (username); g_free (username); /* check if we have a valid entry */ if (G_UNLIKELY (passwd == NULL)) { username = g_strndup (remainder, slash - remainder); g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, _("Unknown user \"%s\""), username); g_free (username); return NULL; } /* use the homedir of the specified user */ replacement = passwd->pw_dir; } /* generate the filename */ return g_build_filename (replacement, slash, NULL); } else if (*filename == '$') { /* examine the remainder of the variable and filename */ remainder = filename + 1; /* lookup the slash at the end of the variable */ for (slash = remainder; *slash != '\0' && *slash != G_DIR_SEPARATOR; ++slash) ; /* get the variable for replacement */ variable = g_strndup (remainder, slash - remainder); replacement = g_getenv (variable); g_free (variable); if (replacement == NULL) return NULL; /* generate the filename */ return g_build_filename (replacement, slash, NULL); } else if (*filename == '.') { /* examine the remainder of the filename */ remainder = filename + 1; /* transform working directory into a filename string */ if (G_LIKELY (working_directory != NULL)) { pwd = g_file_get_path (working_directory); /* if we only have the slash then we want the working directory only */ if (G_UNLIKELY (*remainder == '\0')) return pwd; /* concatenate working directory and remainder */ result = g_build_filename (pwd, remainder, G_DIR_SEPARATOR_S, NULL); /* free the working directory string */ g_free (pwd); } else result = g_strdup (filename); /* return the resulting path string */ return result; } return g_strdup (filename); } /** * thunar_util_humanize_file_time: * @file_time : a #guint64 timestamp. * @date_style : the #ThunarDateFormat used to humanize the @file_time. * @date_custom_style : custom style to apply, if @date_style is set to custom * * Returns a human readable date representation of the specified * @file_time. The caller is responsible to free the returned * string using g_free() when no longer needed. * * Return value: a human readable date representation of @file_time * according to the @date_format. **/ gchar * thunar_util_humanize_file_time (guint64 file_time, ThunarDateStyle date_style, const gchar *date_custom_style) { const gchar *date_format; gchar *time_str; GDateTime *dt_file; GDate dfile; GDate dnow; gint diff; /* check if the file_time is valid */ if (G_UNLIKELY (file_time == 0)) return g_strdup (_("Unknown")); if ((dt_file = g_date_time_new_from_unix_local (file_time)) == NULL) return g_strdup (_("Unknown")); /* check which style to use to format the time */ if (date_style == THUNAR_DATE_STYLE_SIMPLE || date_style == THUNAR_DATE_STYLE_SHORT || date_style == THUNAR_DATE_STYLE_CUSTOM_SIMPLE) { /* setup the dates for the time values */ g_date_set_time_t (&dfile, (time_t) file_time); g_date_set_time_t (&dnow, time (NULL)); /* determine the difference in days */ diff = g_date_get_julian (&dnow) - g_date_get_julian (&dfile); if (diff == 0) { if (date_style == THUNAR_DATE_STYLE_SIMPLE || date_style == THUNAR_DATE_STYLE_CUSTOM_SIMPLE) { /* TRANSLATORS: file was modified less than one day ago */ time_str = g_strdup (_("Today")); } else /* if (date_style == THUNAR_DATE_STYLE_SHORT) */ { /* TRANSLATORS: file was modified less than one day ago */ time_str = g_date_time_format (dt_file, _("Today at %X")); } } else if (diff == 1) { if (date_style == THUNAR_DATE_STYLE_SIMPLE || date_style == THUNAR_DATE_STYLE_CUSTOM_SIMPLE) { /* TRANSLATORS: file was modified less than two days ago */ time_str = g_strdup (_("Yesterday")); } else /* if (date_style == THUNAR_DATE_STYLE_SHORT) */ { /* TRANSLATORS: file was modified less than two days ago */ time_str = g_date_time_format (dt_file, _("Yesterday at %X")); } } else { if (date_style == THUNAR_DATE_STYLE_CUSTOM_SIMPLE) { if (date_custom_style == NULL) time_str = g_strdup (""); else /* use custom date formatting */ time_str = g_date_time_format (dt_file, date_custom_style); } else { if (diff > 1 && diff < 7) { /* Days from last week */ date_format = (date_style == THUNAR_DATE_STYLE_SIMPLE) ? "%A" : _("%A at %X"); } else { /* Any other date */ date_format = (date_style == THUNAR_DATE_STYLE_SIMPLE) ? "%x" : _("%x at %X"); } /* format the date string accordingly */ time_str = g_date_time_format (dt_file, date_format); } } } else if (date_style == THUNAR_DATE_STYLE_LONG) { /* use long, date(1)-like format string */ time_str = g_date_time_format (dt_file, "%c"); } else if (date_style == THUNAR_DATE_STYLE_YYYYMMDD) { time_str = g_date_time_format (dt_file, "%Y-%m-%d %H:%M:%S"); } else if (date_style == THUNAR_DATE_STYLE_MMDDYYYY) { time_str = g_date_time_format (dt_file, "%m-%d-%Y %H:%M:%S"); } else if (date_style == THUNAR_DATE_STYLE_DDMMYYYY) { time_str = g_date_time_format (dt_file, "%d-%m-%Y %H:%M:%S"); } else /* if (date_style == THUNAR_DATE_STYLE_CUSTOM) */ { if (date_custom_style == NULL) time_str = g_strdup (""); else /* use custom date formatting */ time_str = g_date_time_format (dt_file, date_custom_style); } g_date_time_unref (dt_file); return time_str; } /** * thunar_util_parse_parent: * @parent : a #GtkWidget, a #GdkScreen or %NULL. * @window_return : return location for the toplevel #GtkWindow or * %NULL. * * Determines the screen for the @parent and returns that #GdkScreen. * If @window_return is not %NULL, the pointer to the #GtkWindow is * placed into it, or %NULL if the window could not be determined. * * Return value: the #GdkScreen for the @parent. **/ GdkScreen * thunar_util_parse_parent (gpointer parent, GtkWindow **window_return) { GdkScreen *screen; GtkWidget *window = NULL; _thunar_return_val_if_fail (parent == NULL || GDK_IS_SCREEN (parent) || GTK_IS_WIDGET (parent), NULL); /* determine the proper parent */ if (parent == NULL) { /* just use the default screen then */ screen = gdk_screen_get_default (); } else if (GDK_IS_SCREEN (parent)) { /* yep, that's a screen */ screen = GDK_SCREEN (parent); } else { /* parent is a widget, so let's determine the toplevel window */ window = gtk_widget_get_toplevel (GTK_WIDGET (parent)); if (window != NULL && gtk_widget_is_toplevel (window)) { /* make sure the toplevel window is shown */ gtk_widget_show_now (window); } else { /* no toplevel, not usable then */ window = NULL; } /* determine the screen for the widget */ screen = gtk_widget_get_screen (GTK_WIDGET (parent)); } /* check if we should return the window */ if (G_LIKELY (window_return != NULL)) { /* If we dont know the toplevel window, try to return the topmost thunar window */ if (window == NULL) { ThunarApplication *application; GList *window_list; application = thunar_application_get (); window_list = thunar_application_get_windows (application); if (window_list != NULL) { window = g_list_last (window_list)->data; /* this will be the topmost Window */ g_list_free (window_list); } g_object_unref (G_OBJECT (application)); } *window_return = (GtkWindow *) window; } return screen; } /** * thunar_util_time_from_rfc3339: * @date_string : an RFC 3339 encoded date string. * * Decodes the @date_string, which must be in the special RFC 3339 * format YYYY-MM-DDThh:mm:ss. This method is * used to decode deletion dates of files in the trash. See the * Trash Specification for details. * * Return value: the time value matching the @date_string or * %0 if the @date_string could not be parsed. **/ time_t thunar_util_time_from_rfc3339 (const gchar *date_string) { struct tm tm; #ifdef HAVE_STRPTIME /* using strptime() its easy to parse the date string */ if (G_UNLIKELY (strptime (date_string, "%FT%T", &tm) == NULL)) return 0; #else gulong val; /* be sure to start with a clean tm */ memset (&tm, 0, sizeof (tm)); /* parsing by hand is also doable for RFC 3339 dates */ val = strtoul (date_string, (gchar **) &date_string, 10); if (G_UNLIKELY (*date_string != '-')) return 0; /* YYYY-MM-DD */ tm.tm_year = val - 1900; date_string++; tm.tm_mon = strtoul (date_string, (gchar **) &date_string, 10) - 1; if (G_UNLIKELY (*date_string++ != '-')) return 0; tm.tm_mday = strtoul (date_string, (gchar **) &date_string, 10); if (G_UNLIKELY (*date_string++ != 'T')) return 0; val = strtoul (date_string, (gchar **) &date_string, 10); if (G_UNLIKELY (*date_string != ':')) return 0; /* hh:mm:ss */ tm.tm_hour = val; date_string++; tm.tm_min = strtoul (date_string, (gchar **) &date_string, 10); if (G_UNLIKELY (*date_string++ != ':')) return 0; tm.tm_sec = strtoul (date_string, (gchar **) &date_string, 10); #endif /* !HAVE_STRPTIME */ /* translate tm to time_t */ return mktime (&tm); } gchar * thunar_util_change_working_directory (const gchar *new_directory) { gchar *old_directory; _thunar_return_val_if_fail (new_directory != NULL && *new_directory != '\0', NULL); /* try to determine the current working directory */ old_directory = g_get_current_dir (); /* try switching to the new working directory */ if (g_chdir (new_directory) != 0) { /* switching failed, we don't need to return the old directory */ g_free (old_directory); old_directory = NULL; } return old_directory; } void thunar_setup_display_cb (gpointer data) { g_setenv ("DISPLAY", (char *) data, TRUE); } /* * thunar_util_next_new_file_name * * Simplification of "thunar_util_next_new_file_name_raw" by using a ThunarFolder */ gchar * thunar_util_next_new_file_name (ThunarFile *dir, const gchar *file_name, ThunarNextFileNameMode name_mode, gboolean is_directory) { gchar *new_name = NULL; ThunarFolder *folder; GList *files_in_folder = NULL; GHashTableIter iter; gpointer key; folder = thunar_folder_get_for_file (dir); g_hash_table_iter_init (&iter, thunar_folder_get_files (folder)); while (g_hash_table_iter_next (&iter, &key, NULL)) files_in_folder = g_list_append (files_in_folder, thunar_file_get_file (key)); new_name = thunar_util_next_new_file_name_raw (files_in_folder, file_name, name_mode, is_directory); g_list_free (files_in_folder); g_object_unref (folder); return new_name; } /** * thunar_util_next_new_file_name_raw * @file_list List of GFiles to check for the name * @file_name : the filename which will be used as the basis/default * @ThunarNextFileNameMode: To decide if the naming should follow "file copy","file link" or "new file" syntax * @is_directory: TRUE, if @file_name is supposed to be the name of a directory * * Returns a filename that is like @file_name with the possible addition of * a number to differentiate it from other similarly named files. In other words * it searches @dir for incrementally named files starting from @file_name * and returns the first available increment. * * e.g. in a folder with the following files: * - file * - empty * - file_copy * * Calling this functions with the above folder and @file_name equal to 'file' the returned * filename will be 'file 1' for the mode "new file". * * The caller is responsible to free the returned string using g_free() when no longer needed. * * Return value: pointer to the new filename. **/ gchar * thunar_util_next_new_file_name_raw (GList *file_list, const gchar *file_name, ThunarNextFileNameMode name_mode, gboolean is_directory) { unsigned long file_name_size = strlen (file_name); unsigned count = 0; gboolean found_duplicate = FALSE; gchar *extension = NULL; gchar *new_name = g_strdup (file_name); /* get file extension if file is not a directory */ if (!is_directory) extension = thunar_util_str_get_extension (file_name); /* if the file has an extension don't include it in the search */ if (extension != NULL) file_name_size -= strlen (extension); /* loop through the directory until new_name is unique */ while (TRUE) { found_duplicate = FALSE; for (GList *lp = file_list; lp != NULL; lp = lp->next) { gchar *name = g_file_get_basename (lp->data); if (g_strcmp0 (new_name, name) == 0) { found_duplicate = TRUE; g_free (name); break; } g_free (name); } if (!found_duplicate) break; g_free (new_name); if (name_mode == THUNAR_NEXT_FILE_NAME_MODE_NEW) new_name = g_strdup_printf (_("%.*s %u%s"), (int) file_name_size, file_name, ++count, extension ? extension : ""); else if (name_mode == THUNAR_NEXT_FILE_NAME_MODE_COPY) new_name = g_strdup_printf (_("%.*s (copy %u)%s"), (int) file_name_size, file_name, ++count, extension ? extension : ""); else if (name_mode == THUNAR_NEXT_FILE_NAME_MODE_LINK) { if (count == 0) { new_name = g_strdup_printf (_("link to %.*s%s"), (int) file_name_size, file_name, extension ? extension : ""); ++count; } else { new_name = g_strdup_printf (_("link %u to %.*s%s"), ++count, (int) file_name_size, file_name, extension ? extension : ""); } } else g_assert ("should not be reached"); } return new_name; } /** * thunar_util_get_search_prefix * * Return value: The localized search prefix **/ const char * thunar_util_get_search_prefix (void) { return _("Search: "); } /** * thunar_util_is_a_search_query * @string : the string to check * * Return value: a boolean that is TRUE if @string starts with 'Search: '. **/ gboolean thunar_util_is_a_search_query (const gchar *string) { return strncmp (string, thunar_util_get_search_prefix (), strlen (thunar_util_get_search_prefix ())) == 0; } /** * thunar_util_strjoin_list: * @data : list of strings which needs to be appended. * @separator : text which needs to be added as a seperator * * Joins a number of strings together to form one long string, with the optional separator * inserted between each of them. It skips all the NULL values passed in the list. The returned * string should be freed with g_free(). * Similar to g_strjoin(). * * The caller is responsible to free the returned text using g_free() when it is no longer needed * * Return value: the concatenated string **/ gchar * thunar_util_strjoin_list (GList *string_list, const gchar *separator) { GList *lp; gchar *joined_string = NULL; gchar *temp_string; for (lp = string_list; lp != NULL; lp = lp->next) { if (xfce_str_is_empty ((gchar *) lp->data)) continue; if (joined_string == NULL) { joined_string = g_strdup (lp->data); } else { temp_string = g_strjoin (separator, joined_string, lp->data, NULL); g_free (joined_string); joined_string = temp_string; } } if (joined_string == NULL) return g_strdup (""); else return joined_string; } static void thunar_util_determine_corner_properties (GtkWidget *widget, GtkCellRenderer *cell, gint cell_height, gint cell_width, gdouble *radius, gboolean *side) { GtkTextDirection text_direction; /* only have rounded corners for icon view. */ if (G_LIKELY (EXO_IS_ICON_VIEW (widget))) { if (exo_icon_view_get_orientation (EXO_ICON_VIEW (widget)) == GTK_ORIENTATION_HORIZONTAL) { /* Compact View */ /* determine the radius proportional to either height or width (depens on the view) */ *radius = cell_height * (BORDER_RADIUS / 100.0); text_direction = gtk_widget_get_direction (widget); /* decide which side to draw the rounded corners */ if (THUNAR_IS_TEXT_RENDERER (cell)) *side = text_direction == GTK_TEXT_DIR_LTR ? DRAW_ON_RIGHT : DRAW_ON_LEFT; else *side = text_direction == GTK_TEXT_DIR_LTR ? DRAW_ON_LEFT : DRAW_ON_RIGHT; } else { /* Icon View */ *radius = cell_width * (BORDER_RADIUS / 100.0); /* text direction has no effect on this view */ /* decide which side to draw the rounded corners */ if (THUNAR_IS_TEXT_RENDERER (cell)) *side = DRAW_ON_BOTTOM; else *side = DRAW_ON_TOP; } } } static void thunar_util_draw_rounded_corners (cairo_t *cr, const GdkRectangle *background_area, gdouble radius, gint side) { gdouble *corner_radius; gdouble draw_round_corners_on_left[4] = { 0, 0, radius, radius }; gdouble draw_round_corners_on_right[4] = { radius, radius, 0, 0 }; gdouble draw_round_corners_on_top[4] = { radius, 0, 0, radius }; gdouble draw_round_corners_on_bottom[4] = { 0, radius, radius, 0 }; gdouble draw_round_corners_on_all_sides[4] = { radius, radius, radius, radius }; gdouble degrees = G_PI / 180.0; switch (side) { case DRAW_ON_LEFT: corner_radius = draw_round_corners_on_left; break; case DRAW_ON_RIGHT: corner_radius = draw_round_corners_on_right; break; case DRAW_ON_BOTTOM: corner_radius = draw_round_corners_on_bottom; break; case DRAW_ON_TOP: corner_radius = draw_round_corners_on_top; break; case DRAW_ON_ALL_SIDES: corner_radius = draw_round_corners_on_all_sides; break; default: corner_radius = draw_round_corners_on_all_sides; g_warn_if_reached (); } cairo_new_sub_path (cr); cairo_arc (cr, background_area->x + background_area->width - corner_radius[THUNAR_CELL_TOP_RIGHT], /* cairo x coord */ background_area->y + corner_radius[THUNAR_CELL_TOP_RIGHT], /* cairo y coord */ corner_radius[THUNAR_CELL_TOP_RIGHT], -90 * degrees, 0 * degrees); /* radius, angle1, angle2 resp. */ cairo_arc (cr, background_area->x + background_area->width - corner_radius[THUNAR_CELL_BOTTOM_RIGHT], background_area->y + background_area->height - corner_radius[THUNAR_CELL_BOTTOM_RIGHT], corner_radius[THUNAR_CELL_BOTTOM_RIGHT], 0 * degrees, 90 * degrees); cairo_arc (cr, background_area->x + corner_radius[THUNAR_CELL_TOP_LEFT], background_area->y + background_area->height - corner_radius[THUNAR_CELL_TOP_LEFT], corner_radius[THUNAR_CELL_TOP_LEFT], 90 * degrees, 180 * degrees); cairo_arc (cr, background_area->x + corner_radius[THUNAR_CELL_BOTTOM_LEFT], background_area->y + corner_radius[THUNAR_CELL_BOTTOM_LEFT], corner_radius[THUNAR_CELL_BOTTOM_LEFT], 180 * degrees, 270 * degrees); cairo_close_path (cr); cairo_clip (cr); } void thunar_util_clip_view_background (GtkCellRenderer *cell, cairo_t *cr, const GdkRectangle *background_area, GtkWidget *widget, GtkCellRendererState flags) { GtkStyleContext *context; GdkRGBA *color = NULL; GdkRGBA highlight_color_rgba; gboolean color_selected = (flags & GTK_CELL_RENDERER_SELECTED) != 0; gboolean rounded_corners; gchar *highlight_color; gdouble radius = 0.0; gint side = DRAW_ON_ALL_SIDES; GtkWidget *toplevel; gboolean window_is_backdrop = TRUE; g_object_get (G_OBJECT (cell), "highlight-color", &highlight_color, "rounded-corners", &rounded_corners, NULL); cairo_save (cr); if (G_LIKELY (rounded_corners)) { /* determine radius & the side to draw the rounded corners */ thunar_util_determine_corner_properties (widget, cell, background_area->height, background_area->width, &radius, &side); thunar_util_draw_rounded_corners (cr, background_area, radius, side); } if (G_UNLIKELY (highlight_color != NULL)) { gdk_rgba_parse (&highlight_color_rgba, highlight_color); color = gdk_rgba_copy (&highlight_color_rgba); } /* check the state of our window (active/backdrop) */ toplevel = gtk_widget_get_toplevel (widget); if (GTK_IS_WINDOW (toplevel) && gtk_window_is_active (GTK_WINDOW (toplevel))) window_is_backdrop = FALSE; /** * If the item is selected then paint the background area with the theme's selected item's color. * To distinguish between highlighted & non highlighted files, the background area of icon renderer * is left untouched if it already has a highlight color **/ if (G_UNLIKELY (color_selected && !(THUNAR_IS_ICON_RENDERER (cell) && highlight_color != NULL))) { context = gtk_widget_get_style_context (widget); if (window_is_backdrop) gtk_style_context_lookup_color (context, "theme_unfocused_selected_bg_color", &highlight_color_rgba); else gtk_style_context_lookup_color (context, "theme_selected_bg_color", &highlight_color_rgba); if (color) gdk_rgba_free (color); color = gdk_rgba_copy (&highlight_color_rgba); } if (G_LIKELY (color != NULL)) { gdk_cairo_set_source_rgba (cr, color); gdk_rgba_free (color); cairo_paint_with_alpha (cr, window_is_backdrop ? ALPHA_BACKDROP : ALPHA_FOCUSED); } g_free (highlight_color); cairo_restore (cr); } /** * thunar_util_split_search_query: * @search_query: The search query to split. * @error: Return location for regex compilation errors. * * Search terms are split on whitespace. Search queries must be * normalized before passing to this function. * * See also: thunar_g_utf8_normalize_for_search(). * * Return value: a list of search terms which must be freed with g_strfreev() **/ gchar ** thunar_util_split_search_query (const gchar *search_query_normalized, GError **error) { GRegex *whitespace_regex; gchar **search_terms; whitespace_regex = g_regex_new ("\\s+", 0, 0, error); if (whitespace_regex == NULL) return NULL; search_terms = g_regex_split (whitespace_regex, search_query_normalized, 0); g_regex_unref (whitespace_regex); return search_terms; } /** * thunar_util_search_terms_match: * @terms: The search terms to look for, prepared with thunar_util_split_search_query(). * @str: The string which the search terms might be found in. * * All search terms must match. Thunar uses simple substring matching * for the broadest multilingual support. @str must be normalized before * passing to this function. * * See also: thunar_g_utf8_normalize_for_search(). * * Return value: TRUE if all terms matched, FALSE otherwise. **/ gboolean thunar_util_search_terms_match (gchar **terms, gchar *str) { for (gint i = 0; terms[i] != NULL; i++) if (g_strrstr (str, terms[i]) == NULL) return FALSE; return TRUE; } gboolean thunar_util_save_geometry_timer (gpointer user_data) { GdkWindowState state; ThunarPreferences *preferences; gboolean remember_geometry; gint width; gint height; preferences = thunar_preferences_get (); g_object_get (G_OBJECT (preferences), "misc-remember-geometry", &remember_geometry, NULL); /* check if we should remember the window geometry */ if (G_LIKELY (remember_geometry)) { /* check if the window is still visible */ if (gtk_widget_get_visible (user_data)) { /* determine the current state of the window */ state = gdk_window_get_state (gtk_widget_get_window (user_data)); /* don't save geometry for maximized or fullscreen windows */ if ((state & (GDK_WINDOW_STATE_MAXIMIZED | GDK_WINDOW_STATE_FULLSCREEN)) == 0) { /* determine the current width/height of the window... */ gtk_window_get_size (GTK_WINDOW (user_data), &width, &height); /* ...and remember them as default for new windows */ if (THUNAR_IS_WINDOW (user_data)) g_object_set (G_OBJECT (preferences), "last-window-width", width, "last-window-height", height, "last-window-maximized", FALSE, NULL); else if (THUNAR_IS_RENAMER_DIALOG (user_data)) g_object_set (G_OBJECT (preferences), "last-renamer-dialog-width", width, "last-renamer-dialog-height", height, "last-renamer-dialog-maximized", FALSE, NULL); } else { /* only store that the window is full screen */ if (THUNAR_IS_WINDOW (user_data)) g_object_set (G_OBJECT (preferences), "last-window-maximized", TRUE, NULL); else if (THUNAR_IS_RENAMER_DIALOG (user_data)) g_object_set (G_OBJECT (preferences), "last-renamer-dialog-maximized", TRUE, NULL); } } } g_object_unref (preferences); return G_SOURCE_REMOVE; } /** * thunar_util_get_file_time: * @file_info : a #GFileInfo instance. * @date_type : the kind of date you are interested in. * * Queries the given @date_type from @file_info and returns the result. * * Return value: the time for @file_info of the given @date_type. **/ guint64 thunar_util_get_file_time (GFileInfo *file_info, ThunarFileDateType date_type) { const gchar *attribute; GDateTime *datetime; gint64 date; if (file_info == NULL) return 0; switch (date_type) { case THUNAR_FILE_DATE_ACCESSED: attribute = G_FILE_ATTRIBUTE_TIME_ACCESS; break; case THUNAR_FILE_DATE_CHANGED: attribute = G_FILE_ATTRIBUTE_TIME_CHANGED; break; case THUNAR_FILE_DATE_CREATED: attribute = G_FILE_ATTRIBUTE_TIME_CREATED; break; case THUNAR_FILE_DATE_MODIFIED: attribute = G_FILE_ATTRIBUTE_TIME_MODIFIED; break; case THUNAR_FILE_DATE_DELETED: datetime = g_file_info_get_deletion_date (file_info); if (datetime == NULL) return 0; date = g_date_time_to_unix (datetime); g_date_time_unref (datetime); return date; default: g_warn_if_reached (); return 0; } return g_file_info_get_attribute_uint64 (file_info, attribute); } /** * thunar_util_get_statusbar_text_for_files: * @files : list of #GFile's for which a text is requested * @show_hidden : whether hidden files are shown * @show_file_size_binary_format : whether the file size should be displayed in binary format * @date_style : the #ThunarDateFormat used to humanize the @file_time. * @date_custom_style : custom style to apply, if @date_style is set to custom * * Generates the statusbar text for the given @files. * * The caller is reponsible to free the returned text using * g_free() when it's no longer needed. * * Return value: the statusbar text with the given @files. **/ gchar * thunar_util_get_statusbar_text_for_files (GHashTable *files, gboolean show_hidden, gboolean show_file_size_binary_format, ThunarDateStyle date_style, const gchar *date_custom_style, guint status_bar_actve_info) { guint64 size_summary = 0; gint folder_count = 0, hidden_folder_count = 0; gint file_count = 0, hidden_file_count = 0; GList *text_list = NULL; gchar *size_string = NULL; gchar *temp_string = NULL; gchar *folder_text = NULL; gchar *file_text = NULL; guint64 last_modified_date = 0; guint64 temp_last_modified_date; GFileInfo *last_modified_file = NULL; gboolean show_hidden_count, show_size, show_size_in_bytes, show_last_modified; GHashTableIter iter; gpointer key; show_hidden_count = thunar_status_bar_info_check_active (status_bar_actve_info, THUNAR_STATUS_BAR_INFO_HIDDEN_COUNT); show_size = thunar_status_bar_info_check_active (status_bar_actve_info, THUNAR_STATUS_BAR_INFO_SIZE); show_size_in_bytes = thunar_status_bar_info_check_active (status_bar_actve_info, THUNAR_STATUS_BAR_INFO_SIZE_IN_BYTES); show_last_modified = thunar_status_bar_info_check_active (status_bar_actve_info, THUNAR_STATUS_BAR_INFO_LAST_MODIFIED); /* analyze files */ g_hash_table_iter_init (&iter, files); while (g_hash_table_iter_next (&iter, &key, NULL)) { GFileInfo *file_info = g_file_query_info (G_FILE (key), G_FILE_ATTRIBUTE_STANDARD_TYPE "," G_FILE_ATTRIBUTE_STANDARD_SIZE "," G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP "," G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NONE, NULL, NULL); if (file_info == NULL) continue; if (g_file_info_get_attribute_uint32 (file_info, G_FILE_ATTRIBUTE_STANDARD_TYPE) == G_FILE_TYPE_DIRECTORY) { folder_count++; if (g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN) || g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP)) hidden_folder_count++; } else { file_count++; if (g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN) || g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_STANDARD_IS_BACKUP)) hidden_file_count++; if (g_file_info_get_attribute_uint32 (file_info, G_FILE_ATTRIBUTE_STANDARD_TYPE) == G_FILE_TYPE_REGULAR) g_file_info_get_attribute_uint64 (file_info, G_FILE_ATTRIBUTE_STANDARD_SIZE); size_summary += g_file_info_get_attribute_uint64 (file_info, G_FILE_ATTRIBUTE_STANDARD_SIZE); } temp_last_modified_date = thunar_util_get_file_time (file_info, THUNAR_FILE_DATE_MODIFIED); if (last_modified_date <= temp_last_modified_date) { last_modified_date = temp_last_modified_date; /* Keep the last modified file-info for later */ if (last_modified_file != NULL) g_object_unref (last_modified_file); last_modified_file = g_object_ref (file_info); } g_object_unref (file_info); } if (file_count > 0) { file_text = g_strdup_printf (ngettext ("%d file", "%d files", file_count), file_count); if (show_hidden_count == TRUE && show_hidden == FALSE && hidden_file_count > 0) { temp_string = g_strdup_printf (_ ("%s (%d hidden)"), file_text, hidden_file_count); g_free (file_text); file_text = temp_string; } if (show_size == TRUE) { if (show_size_in_bytes == TRUE) { size_string = g_format_size_full (size_summary, G_FORMAT_SIZE_LONG_FORMAT | (show_file_size_binary_format ? G_FORMAT_SIZE_IEC_UNITS : G_FORMAT_SIZE_DEFAULT)); } else { size_string = g_format_size_full (size_summary, show_file_size_binary_format ? G_FORMAT_SIZE_IEC_UNITS : G_FORMAT_SIZE_DEFAULT); } temp_string = g_strdup_printf (_ ("%s: %s"), file_text, size_string); g_free (file_text); g_free (size_string); file_text = temp_string; } } if (folder_count > 0) { folder_text = g_strdup_printf (ngettext ("%d folder", "%d folders", folder_count), folder_count); if (show_hidden_count == TRUE && show_hidden == FALSE && hidden_folder_count > 0) { temp_string = g_strdup_printf (_ ("%s (%d hidden)"), folder_text, hidden_folder_count); g_free (folder_text); folder_text = temp_string; } } if (file_text == NULL && folder_text == NULL) text_list = g_list_append (text_list, g_strdup_printf (_ ("0 items"))); if (folder_text != NULL) text_list = g_list_append (text_list, folder_text); if (file_text != NULL) text_list = g_list_append (text_list, file_text); if (show_last_modified && last_modified_file != NULL) { temp_string = thunar_util_humanize_file_time (thunar_util_get_file_time (last_modified_file, THUNAR_FILE_DATE_MODIFIED), date_style, date_custom_style); text_list = g_list_append (text_list, g_strdup_printf (_ ("Last Modified: %s"), temp_string)); g_free (temp_string); } temp_string = thunar_util_strjoin_list (text_list, " | "); g_list_free_full (text_list, g_free); if (last_modified_file != NULL) g_object_unref (last_modified_file); return temp_string; } gchar * thunar_util_get_statusbar_text_for_single_file (ThunarFile *file) { const gchar *content_type; const gchar *original_path; GList *text_list = NULL; gchar *temp_string = NULL; gchar *text = ""; gint height; gint width; ThunarPreferences *preferences; gboolean show_image_size; gboolean show_file_size_binary_format; guint active; gboolean show_size, show_size_in_bytes, show_filetype, show_display_name, show_last_modified; ThunarDateStyle date_style; gchar *date_custom_style; gchar *date_string; preferences = thunar_preferences_get (); g_object_get (G_OBJECT (preferences), "misc-date-style", &date_style, "misc_date-custom-style", &date_custom_style, "misc-status-bar-active-info", &active, "misc-file-size-binary", &show_file_size_binary_format, NULL); show_size = thunar_status_bar_info_check_active (active, THUNAR_STATUS_BAR_INFO_SIZE); show_size_in_bytes = thunar_status_bar_info_check_active (active, THUNAR_STATUS_BAR_INFO_SIZE_IN_BYTES); show_filetype = thunar_status_bar_info_check_active (active, THUNAR_STATUS_BAR_INFO_FILETYPE); show_display_name = thunar_status_bar_info_check_active (active, THUNAR_STATUS_BAR_INFO_DISPLAY_NAME); show_last_modified = thunar_status_bar_info_check_active (active, THUNAR_STATUS_BAR_INFO_LAST_MODIFIED); /* determine the content type of the file */ content_type = thunar_file_get_content_type (file); if (show_display_name == TRUE) { if (g_strcmp0 (thunar_file_get_display_name (file), thunar_file_get_basename (file)) != 0) temp_string = g_strdup_printf (_ ("\"%s\" (%s)"), thunar_file_get_display_name (file), thunar_file_get_basename (file)); else temp_string = g_strdup_printf (_ ("\"%s\""), thunar_file_get_display_name (file)); text_list = g_list_append (text_list, temp_string); } if (thunar_file_is_regular (file) || G_UNLIKELY (thunar_file_is_symlink (file))) { if (show_size == TRUE) { if (show_size_in_bytes == TRUE) temp_string = thunar_file_get_size_string_long (file, show_file_size_binary_format); else temp_string = thunar_file_get_size_string_formatted (file, show_file_size_binary_format); text_list = g_list_append (text_list, temp_string); } } if (show_filetype == TRUE) { if (G_UNLIKELY (content_type != NULL && g_str_equal (content_type, "inode/symlink"))) temp_string = g_strdup (_ ("broken link")); else if (G_UNLIKELY (thunar_file_is_symlink (file))) temp_string = g_strdup_printf (_ ("link to %s"), thunar_file_get_symlink_target (file)); else if (G_UNLIKELY (thunar_file_get_kind (file) == G_FILE_TYPE_SHORTCUT)) temp_string = g_strdup (_ ("shortcut")); else if (G_UNLIKELY (thunar_file_get_kind (file) == G_FILE_TYPE_MOUNTABLE)) temp_string = g_strdup (_ ("mountable")); else if (G_UNLIKELY (thunar_file_is_mountpoint (file))) temp_string = g_strdup (_ ("mount point")); else if (G_LIKELY (content_type != NULL)) { gchar *description = g_content_type_get_description (content_type); temp_string = g_strdup (description); g_free (description); } text_list = g_list_append (text_list, temp_string); } /* append the original path (if any) */ original_path = thunar_file_get_original_path (file); if (G_UNLIKELY (original_path != NULL)) { /* append the original path to the statusbar text */ gchar *original_path_string = g_filename_display_name (original_path); temp_string = g_strdup_printf ("%s %s", _ ("Original Path:"), original_path_string); text_list = g_list_append (text_list, temp_string); g_free (original_path_string); } else if (thunar_file_is_local (file) && thunar_file_is_regular (file) && g_str_has_prefix (content_type, "image/")) /* bug #2913 */ { /* check if the size should be visible in the statusbar, disabled by * default to avoid high i/o */ g_object_get (preferences, "misc-image-size-in-statusbar", &show_image_size, NULL); if (show_image_size) { /* check if we can determine the dimension of this file (only for image files) */ gchar *file_path = g_file_get_path (thunar_file_get_file (file)); if (file_path != NULL && gdk_pixbuf_get_file_info (file_path, &width, &height) != NULL) { /* append the image dimensions to the statusbar text */ temp_string = g_strdup_printf ("%s %dx%d", _ ("Image Size:"), width, height); text_list = g_list_append (text_list, temp_string); } g_free (file_path); } } if (show_last_modified) { date_string = thunar_file_get_date_string (file, THUNAR_FILE_DATE_MODIFIED, date_style, date_custom_style); temp_string = g_strdup_printf (_ ("Last Modified: %s"), date_string); text_list = g_list_append (text_list, temp_string); g_free (date_string); } g_free (date_custom_style); text = thunar_util_strjoin_list (text_list, " | "); g_list_free_full (text_list, g_free); g_object_unref (preferences); return text; } gchar * thunar_util_accel_path_to_id (const gchar *accel_path) { const char *p; if (accel_path == NULL) return NULL; /* Example: "/ThunarWindow/reload" -> "reload" */ p = g_strrstr (accel_path, "/"); if (*p++ == '\0') return NULL; return g_strdup (p); }