Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add sort criterion by reversed extension segments
The basenames of files are split by dots and then starting from the end
each segment is compared to find a sort order. Example:

    bar.tar.bz2
    foo.tar.bz2
    a.bar.gz
    x.tar.gz
    z.tar.gz
    test.tex

A heuristic determines what extension segments are part of the
extension. There is probably no perfect solution but there are much
less false positives. As a result the sorting is more intuitive and the
displayed column in the list view is better readable and displays
extensions more accurately.

In addition a bug related to the default sort criteria in the
preferences has been fixed.
  • Loading branch information
muesli4 authored and lukefromdc committed Apr 8, 2018
1 parent 68ace00 commit 0585258
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 1 deletion.
7 changes: 7 additions & 0 deletions libcaja-private/caja-column-utilities.c
Expand Up @@ -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;
}

Expand Down
200 changes: 200 additions & 0 deletions libcaja-private/caja-file.c
Expand Up @@ -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>
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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");
Expand Down
3 changes: 2 additions & 1 deletion libcaja-private/caja-file.h
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions libcaja-private/org.mate.caja.gschema.xml
Expand Up @@ -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">
Expand Down
7 changes: 7 additions & 0 deletions src/caja-file-management-properties.c
Expand Up @@ -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",
Expand All @@ -104,6 +110,7 @@ static const char * const sort_order_values[] =
"mtime",
"atime",
"emblems",
"extension",
"trash-time",
NULL
};
Expand Down
6 changes: 6 additions & 0 deletions src/caja-file-management-properties.ui
Expand Up @@ -83,6 +83,9 @@
<row>
<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>
Expand All @@ -95,6 +98,9 @@
<row>
<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>
Expand Down
2 changes: 2 additions & 0 deletions src/file-manager/caja-icon-view-ui.xml
Expand Up @@ -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"/>
Expand All @@ -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"/>
Expand Down
13 changes: 13 additions & 0 deletions src/file-manager/fm-icon-view.c
Expand Up @@ -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")
}
};

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 0585258

Please sign in to comment.