diff options
-rw-r--r-- | libcaja-private/caja-column-utilities.c | 7 | ||||
-rw-r--r-- | libcaja-private/caja-file.c | 200 | ||||
-rw-r--r-- | libcaja-private/caja-file.h | 3 | ||||
-rw-r--r-- | libcaja-private/org.mate.caja.gschema.xml | 1 | ||||
-rw-r--r-- | src/caja-file-management-properties.c | 7 | ||||
-rw-r--r-- | src/caja-file-management-properties.ui | 6 | ||||
-rw-r--r-- | src/file-manager/caja-icon-view-ui.xml | 2 | ||||
-rw-r--r-- | src/file-manager/fm-icon-view.c | 13 |
8 files changed, 238 insertions, 1 deletions
diff --git a/libcaja-private/caja-column-utilities.c b/libcaja-private/caja-column-utilities.c index f63a2034..57dae7f5 100644 --- a/libcaja-private/caja-column-utilities.c +++ b/libcaja-private/caja-column-utilities.c @@ -139,6 +139,13 @@ get_builtin_columns (void) "description", _("The location of the file."), NULL)); + columns = g_list_append (columns, + g_object_new (CAJA_TYPE_COLUMN, + "name", "extension", + "attribute", "extension", + "label", _("Extension"), + "description", _("The extension of the file."), + NULL)); return columns; } diff --git a/libcaja-private/caja-file.c b/libcaja-private/caja-file.c index 0652fd1c..f4807424 100644 --- a/libcaja-private/caja-file.c +++ b/libcaja-private/caja-file.c @@ -66,6 +66,7 @@ #include <libxml/parser.h> #include <pwd.h> #include <stdlib.h> +#include <ctype.h> #include <sys/time.h> #include <time.h> #include <unistd.h> @@ -99,6 +100,9 @@ #define METADATA_ID_IS_LIST_MASK (1<<31) +#define SORT_BY_EXTENSION_FOLLOWING_MAX_LENGTH 3 +#define SORT_BY_EXTENSION_MAX_SEGMENTS 3 + typedef enum { SHOW_HIDDEN = 1 << 0, } FilterOptions; @@ -126,6 +130,7 @@ static GQuark attribute_name_q, attribute_accessed_date_q, attribute_date_accessed_q, attribute_emblems_q, + attribute_extension_q, attribute_mime_type_q, attribute_size_detail_q, attribute_size_on_disk_detail_q, @@ -3160,6 +3165,186 @@ compare_by_full_path (CajaFile *file_1, CajaFile *file_2) return compare_by_display_name (file_1, file_2); } +/* prev_extension_segment: + * @basename The basename of a file + * @rem_chars A pointer to the amount of remaining characters + * + * Finds the next segment delimiter to the left. A starting character of '.' is + * set to '\0'. + * + * Return value: The start of the previous segment (right of the dot) or + * basename if there are none remaining. + */ +static char * +prev_extension_segment (char *basename, int *rem_chars) +{ + if (*basename == '.') { + *basename = 0; + basename--; + (*rem_chars)--; + } + + while (*rem_chars > 0 && *basename != '.') { + (*rem_chars)--; + basename--; + } + + return basename + 1; +} + +/* is_valid_extension_segment: + * @segment Part of a modifiable zero-terminated string + * @segment_index The index of the current segment + * + * Uses a heuristic to identify valid file extensions. + * + * Return value: Whether the segment is part of the file extension. + */ +static gboolean +is_valid_extension_segment (const char *segment, int segment_index) +{ + gboolean result; + gboolean has_letters; + char c; + int char_offset; + switch (segment_index) { + case 0: + /* extremely long segments are probably not part of the extension */ + result = strlen (segment) < 20; + break; + default: + has_letters = FALSE; + char_offset = 0; + while (TRUE) { + c = *(segment + char_offset); + if (c == '\0') { + result = has_letters; + break; + } + /* allow digits if there are also letters */ + else if (isalpha (c)) { + has_letters = TRUE; + } + /* fail if it is neither digit nor letter */ + else if (!isdigit (c)) { + result = FALSE; + break; + } + + if (char_offset >= SORT_BY_EXTENSION_FOLLOWING_MAX_LENGTH) { + result = FALSE; + break; + } + char_offset++; + } + } + return result; +} + +static int +compare_by_extension_segments (CajaFile *file_1, CajaFile *file_2) +{ + char *name_1, *name_2; + char *segment_1, *segment_2; + int compare; + int rem_chars_1, rem_chars_2; + gboolean done_1, done_2; + gboolean is_directory_1, is_directory_2; + int segment_index; + + + /* Directories do not have an extension */ + is_directory_1 = caja_file_is_directory (file_1); + is_directory_2 = caja_file_is_directory (file_2); + + if (is_directory_1 && is_directory_2) { + return 0; + } else if (is_directory_1) { + return -1; + } else if (is_directory_2) { + return 1; + } + + name_1 = caja_file_get_display_name (file_1); + name_2 = caja_file_get_display_name (file_2); + rem_chars_1 = strlen (name_1); + rem_chars_2 = strlen (name_2); + + /* Point to one after the zero character */ + segment_1 = name_1 + rem_chars_1 + 1; + segment_2 = name_2 + rem_chars_2 + 1; + + segment_index = 0; + do { + segment_1 = prev_extension_segment (segment_1 - 1, &rem_chars_1); + segment_2 = prev_extension_segment (segment_2 - 1, &rem_chars_2); + + done_1 = rem_chars_1 <= 0 || !is_valid_extension_segment (segment_1, segment_index); + done_2 = rem_chars_2 <= 0 || !is_valid_extension_segment (segment_2, segment_index); + if (done_1 && !done_2) { + compare = -1; + break; + } + else if (!done_1 && done_2) { + compare = 1; + break; + } + else if (done_1 && done_2) { + compare = 0; + break; + } + + segment_index++; + if (segment_index > SORT_BY_EXTENSION_MAX_SEGMENTS - 1) { + break; + } + compare = strcmp (segment_1, segment_2); + } while (compare == 0); + + g_free (name_1); + g_free (name_2); + + return compare; +} + +static gchar * +caja_file_get_extension_as_string (CajaFile *file) +{ + char *name; + int rem_chars; + int segment_index; + char *segment; + char *right_segment; + char *result; + if (!caja_file_is_directory (file)) { + name = caja_file_get_display_name (file); + rem_chars = strlen (name); + segment = prev_extension_segment (name + rem_chars, &rem_chars); + + if (rem_chars > 0 && is_valid_extension_segment (segment, 0)) { + segment_index = 1; + do { + right_segment = segment; + segment = prev_extension_segment (segment - 1, &rem_chars); + if (rem_chars > 0 && is_valid_extension_segment (segment, segment_index)) { + /* remove zero-termination of segment */ + *(right_segment - 1) = '.'; + } + else { + break; + } + + segment_index++; + } while (segment_index < SORT_BY_EXTENSION_MAX_SEGMENTS + 1); + result = g_strdup (right_segment); + g_free (name); + return result; + } + g_free (name); + } + return g_strdup (""); +} + static int caja_file_compare_for_sort_internal (CajaFile *file_1, CajaFile *file_2, @@ -3286,6 +3471,12 @@ caja_file_compare_for_sort (CajaFile *file_1, result = compare_by_full_path (file_1, file_2); } break; + case CAJA_FILE_SORT_BY_EXTENSION: + result = compare_by_extension_segments (file_1, file_2); + if (result == 0) { + result = compare_by_full_path (file_1, file_2); + } + break; default: g_return_val_if_reached (0); } @@ -3354,6 +3545,11 @@ caja_file_compare_for_sort_by_attribute_q (CajaFile *file_1, CAJA_FILE_SORT_BY_EMBLEMS, directories_first, reversed); + } else if (attribute == attribute_extension_q) { + return caja_file_compare_for_sort (file_1, file_2, + CAJA_FILE_SORT_BY_EXTENSION, + directories_first, + reversed); } /* it is a normal attribute, compare by strings */ @@ -6308,6 +6504,9 @@ caja_file_get_string_attribute_q (CajaFile *file, GQuark attribute_q) return caja_file_get_date_as_string (file, CAJA_DATE_TYPE_PERMISSIONS_CHANGED); } + if (attribute_q == attribute_extension_q) { + return caja_file_get_extension_as_string (file); + } if (attribute_q == attribute_permissions_q) { return caja_file_get_permissions_as_string (file); } @@ -8332,6 +8531,7 @@ caja_file_class_init (CajaFileClass *class) attribute_accessed_date_q = g_quark_from_static_string ("accessed_date"); attribute_date_accessed_q = g_quark_from_static_string ("date_accessed"); attribute_emblems_q = g_quark_from_static_string ("emblems"); + attribute_extension_q = g_quark_from_static_string ("extension"); attribute_mime_type_q = g_quark_from_static_string ("mime_type"); attribute_size_detail_q = g_quark_from_static_string ("size_detail"); attribute_size_on_disk_detail_q = g_quark_from_static_string ("size_on_disk_detail"); diff --git a/libcaja-private/caja-file.h b/libcaja-private/caja-file.h index 6a30b618..949c77e9 100644 --- a/libcaja-private/caja-file.h +++ b/libcaja-private/caja-file.h @@ -64,7 +64,8 @@ typedef enum CAJA_FILE_SORT_BY_ATIME, CAJA_FILE_SORT_BY_EMBLEMS, CAJA_FILE_SORT_BY_TRASHED_TIME, - CAJA_FILE_SORT_BY_SIZE_ON_DISK + CAJA_FILE_SORT_BY_SIZE_ON_DISK, + CAJA_FILE_SORT_BY_EXTENSION } CajaFileSortType; typedef enum diff --git a/libcaja-private/org.mate.caja.gschema.xml b/libcaja-private/org.mate.caja.gschema.xml index b6ecab19..d240ae9a 100644 --- a/libcaja-private/org.mate.caja.gschema.xml +++ b/libcaja-private/org.mate.caja.gschema.xml @@ -39,6 +39,7 @@ <value nick="emblems" value="7"/> <value nick="trash-time" value="8"/> <value nick="size_on_disk" value="9"/> + <value nick="extension" value="10"/> </enum> <enum id="org.mate.caja.ZoomLevel"> diff --git a/src/caja-file-management-properties.c b/src/caja-file-management-properties.c index dc687688..0568f360 100644 --- a/src/caja-file-management-properties.c +++ b/src/caja-file-management-properties.c @@ -94,6 +94,12 @@ static const char * const zoom_values[] = NULL }; +/* + * This array corresponds to the object with id "model2" in + * caja-file-management-properties.ui. It has to positionally match with it. + * The purpose is to map values from a combo box to values of the gsettings + * enum. + */ static const char * const sort_order_values[] = { "name", @@ -104,6 +110,7 @@ static const char * const sort_order_values[] = "mtime", "atime", "emblems", + "extension", "trash-time", NULL }; diff --git a/src/caja-file-management-properties.ui b/src/caja-file-management-properties.ui index 4bb397ea..20d36c08 100644 --- a/src/caja-file-management-properties.ui +++ b/src/caja-file-management-properties.ui @@ -84,6 +84,9 @@ <col id="0" translatable="yes">By Size</col> </row> <row> + <col id="0" translatable="yes">By Size on Disk</col> + </row> + <row> <col id="0" translatable="yes">By Type</col> </row> <row> @@ -96,6 +99,9 @@ <col id="0" translatable="yes">By Emblems</col> </row> <row> + <col id="0" translatable="yes">By Extension</col> + </row> + <row> <col id="0" translatable="yes">By Trashed Date</col> </row> </data> diff --git a/src/file-manager/caja-icon-view-ui.xml b/src/file-manager/caja-icon-view-ui.xml index 89c4cb6e..865daaf3 100644 --- a/src/file-manager/caja-icon-view-ui.xml +++ b/src/file-manager/caja-icon-view-ui.xml @@ -17,6 +17,7 @@ <menuitem name="Sort by Modification Date" action="Sort by Modification Date"/> <menuitem name="Sort by Emblems" action="Sort by Emblems"/> <menuitem name="Sort by Trash Time" action="Sort by Trash Time"/> + <menuitem name="Sort by Extension" action="Sort by Extension"/> </placeholder> <separator name="Layout separator"/> <menuitem name="Tighter Layout" action="Tighter Layout"/> @@ -40,6 +41,7 @@ <menuitem name="Sort by Modification Date" action="Sort by Modification Date"/> <menuitem name="Sort by Emblems" action="Sort by Emblems"/> <menuitem name="Sort by Trash Time" action="Sort by Trash Time"/> + <menuitem name="Sort by Extension" action="Sort by Extension"/> </placeholder> <separator name="Layout separator"/> <menuitem name="Tighter Layout" action="Tighter Layout"/> diff --git a/src/file-manager/fm-icon-view.c b/src/file-manager/fm-icon-view.c index aadb3463..2d533bfc 100644 --- a/src/file-manager/fm-icon-view.c +++ b/src/file-manager/fm-icon-view.c @@ -169,6 +169,13 @@ static const SortCriterion sort_criteria[] = "Sort by Trash Time", N_("by T_rash Time"), N_("Keep icons sorted by trash time in rows") + }, + { + CAJA_FILE_SORT_BY_EXTENSION, + "extension", + "Sort by Extension", + N_("by E_xtension"), + N_("Keep icons sorted by reversed extension segments in rows") } }; @@ -1770,6 +1777,12 @@ static const GtkRadioActionEntry arrange_radio_entries[] = N_("Keep icons sorted by trash time in rows"), CAJA_FILE_SORT_BY_TRASHED_TIME }, + { + "Sort by Extension", NULL, + N_("By E_xtension"), NULL, + N_("Keep icons sorted by reverse extension segments in rows"), + CAJA_FILE_SORT_BY_EXTENSION + }, }; static void |