summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libcaja-private/caja-column-utilities.c7
-rw-r--r--libcaja-private/caja-file.c200
-rw-r--r--libcaja-private/caja-file.h3
-rw-r--r--libcaja-private/org.mate.caja.gschema.xml1
-rw-r--r--src/caja-file-management-properties.c7
-rw-r--r--src/caja-file-management-properties.ui6
-rw-r--r--src/file-manager/caja-icon-view-ui.xml2
-rw-r--r--src/file-manager/fm-icon-view.c13
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