sorting fixes
This commit is contained in:
parent
3bb18fc03e
commit
2ce0bd2ee2
@ -563,15 +563,6 @@ public class AppConfig {
|
||||
properties.setProperty("global.sort.ascending", String.valueOf(asc));
|
||||
}
|
||||
|
||||
// Hidden files ordering: true = hidden after visible (default true)
|
||||
public boolean getHiddenFilesLast() {
|
||||
return Boolean.parseBoolean(properties.getProperty("global.sort.hidden.last", "true"));
|
||||
}
|
||||
|
||||
public void setHiddenFilesLast(boolean last) {
|
||||
properties.setProperty("global.sort.hidden.last", String.valueOf(last));
|
||||
}
|
||||
|
||||
// Uppercase preference: when names equal ignoring case, prefer uppercase first
|
||||
public boolean getUppercasePriority() {
|
||||
return Boolean.parseBoolean(properties.getProperty("global.sort.uppercase.priority", "true"));
|
||||
@ -599,6 +590,15 @@ public class AppConfig {
|
||||
properties.setProperty("global.sort.ignore.leadingdot", String.valueOf(enabled));
|
||||
}
|
||||
|
||||
// Ignore leading dollar sign in names when sorting (treat "$name" as "name")
|
||||
public boolean getIgnoreLeadingDollar() {
|
||||
return Boolean.parseBoolean(properties.getProperty("global.sort.ignore.leadingdollar", "false"));
|
||||
}
|
||||
|
||||
public void setIgnoreLeadingDollar(boolean enabled) {
|
||||
properties.setProperty("global.sort.ignore.leadingdollar", String.valueOf(enabled));
|
||||
}
|
||||
|
||||
public int getAutoRefreshInterval() {
|
||||
return Integer.parseInt(properties.getProperty("panel.autoRefreshInterval", "2000"));
|
||||
}
|
||||
|
||||
@ -296,6 +296,30 @@ public class FilePanel extends JPanel {
|
||||
} catch (Exception ignore) {}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true when the focused component belongs to the drive selector.
|
||||
*/
|
||||
public boolean isDriveSelectorFocusOwner(Component focusedComponent) {
|
||||
if (driveCombo == null || focusedComponent == null) {
|
||||
return false;
|
||||
}
|
||||
return focusedComponent == driveCombo || SwingUtilities.isDescendingFrom(focusedComponent, driveCombo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true when focus should not activate this panel (utility UI controls).
|
||||
*/
|
||||
public boolean isNonActivatingFocusOwner(Component focusedComponent) {
|
||||
if (focusedComponent == null) {
|
||||
return false;
|
||||
}
|
||||
if (isDriveSelectorFocusOwner(focusedComponent)) {
|
||||
return true;
|
||||
}
|
||||
FilePanelTab currentTab = getCurrentTab();
|
||||
return currentTab != null && currentTab.isSortControlFocusOwner(focusedComponent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new tab with a directory
|
||||
|
||||
@ -9,6 +9,7 @@ import cz.kamma.kfmanager.service.FileOperations;
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import javax.swing.table.JTableHeader;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
@ -292,6 +293,28 @@ public class FilePanelTab extends JPanel {
|
||||
fileTable.repaint();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true when focus belongs to one of the BRIEF sorting controls.
|
||||
*/
|
||||
public boolean isSortControlFocusOwner(Component focusedComponent) {
|
||||
if (focusedComponent == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (briefSortPanel != null && briefSortPanel.isVisible() && SwingUtilities.isDescendingFrom(focusedComponent, briefSortPanel)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
JTableHeader header = fileTable != null ? fileTable.getTableHeader() : null;
|
||||
return header != null && SwingUtilities.isDescendingFrom(focusedComponent, header);
|
||||
}
|
||||
|
||||
private void requestTableFocusIfActive() {
|
||||
if (active && fileTable != null) {
|
||||
fileTable.requestFocusInWindow();
|
||||
}
|
||||
}
|
||||
|
||||
private void initComponents() {
|
||||
setLayout(new BorderLayout());
|
||||
@ -851,7 +874,7 @@ public class FilePanelTab extends JPanel {
|
||||
persistedConfig.setDefaultSortAscending(sortAscending);
|
||||
persistedConfig.saveConfig();
|
||||
}
|
||||
fileTable.requestFocus();
|
||||
requestTableFocusIfActive();
|
||||
});
|
||||
|
||||
sortByDateButton.addActionListener(e -> {
|
||||
@ -870,7 +893,7 @@ public class FilePanelTab extends JPanel {
|
||||
persistedConfig.setDefaultSortAscending(sortAscending);
|
||||
persistedConfig.saveConfig();
|
||||
}
|
||||
fileTable.requestFocus();
|
||||
requestTableFocusIfActive();
|
||||
});
|
||||
|
||||
sortBySizeButton.addActionListener(e -> {
|
||||
@ -889,7 +912,7 @@ public class FilePanelTab extends JPanel {
|
||||
persistedConfig.setDefaultSortAscending(sortAscending);
|
||||
persistedConfig.saveConfig();
|
||||
}
|
||||
fileTable.requestFocus();
|
||||
requestTableFocusIfActive();
|
||||
});
|
||||
|
||||
add(briefSortPanel, BorderLayout.NORTH);
|
||||
@ -1387,7 +1410,7 @@ public class FilePanelTab extends JPanel {
|
||||
// Clean up expired timestamps
|
||||
changeTimestamps.entrySet().removeIf(entry -> now - entry.getValue() > CHANGE_HIGHLIGHT_DURATION);
|
||||
|
||||
if (isSameContent(newItems, tableModel.items)) {
|
||||
if (isSameContentIgnoringOrder(newItems, tableModel.items)) {
|
||||
// Nothing changed on disk since last refresh.
|
||||
// Check if we need to clear highlight for items whose duration expired.
|
||||
boolean repainNeeded = false;
|
||||
@ -1442,7 +1465,7 @@ public class FilePanelTab extends JPanel {
|
||||
}
|
||||
|
||||
final List<FileItem> itemsToLoad = newItems;
|
||||
final boolean contentChanged = !isSameContent(newItems, tableModel.items);
|
||||
final boolean contentChanged = !isSameContentIgnoringOrder(newItems, tableModel.items);
|
||||
|
||||
loadDirectory(currentDirectory, itemsToLoad, false, requestFocus, () -> {
|
||||
// Restore marks and set recentlyChanged flag based on active timestamps
|
||||
@ -1547,6 +1570,23 @@ public class FilePanelTab extends JPanel {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isSameContentIgnoringOrder(List<FileItem> list1, List<FileItem> list2) {
|
||||
if (list1.size() != list2.size()) return false;
|
||||
|
||||
Map<String, FileItem> byName = new HashMap<>();
|
||||
for (FileItem item : list1) {
|
||||
byName.put(item.getName(), item);
|
||||
}
|
||||
|
||||
for (FileItem item : list2) {
|
||||
FileItem other = byName.get(item.getName());
|
||||
if (other == null || !other.isSameAs(item)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup previous archive temp dir when navigating away from it.
|
||||
*/
|
||||
@ -3236,11 +3276,92 @@ public class FilePanelTab extends JPanel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove leading and trailing $ characters for sorting purposes.
|
||||
* Normalize name for sorting (currently optional leading '$' ignore).
|
||||
*/
|
||||
private String getCleanNameForSorting(String name) {
|
||||
if (name == null) return "";
|
||||
return name.replaceAll("^\\$+|\\$+$", "");
|
||||
boolean ignoreLeadingDollar = false;
|
||||
try {
|
||||
if (persistedConfig != null) {
|
||||
ignoreLeadingDollar = persistedConfig.getIgnoreLeadingDollar();
|
||||
}
|
||||
} catch (Exception ignore) {}
|
||||
|
||||
if (ignoreLeadingDollar) {
|
||||
return name.replaceFirst("^\\$+", "");
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
private int compareNamesWithConfiguredOptions(FileItem a, FileItem b) {
|
||||
String s1 = getCleanNameForSorting(a.getName());
|
||||
String s2 = getCleanNameForSorting(b.getName());
|
||||
|
||||
try {
|
||||
if (persistedConfig != null && persistedConfig.getIgnoreLeadingDot()) {
|
||||
s1 = s1.replaceFirst("^\\.+", "");
|
||||
s2 = s2.replaceFirst("^\\.+", "");
|
||||
}
|
||||
} catch (Exception ignore) {}
|
||||
|
||||
boolean uppercasePref = true;
|
||||
try {
|
||||
if (persistedConfig != null) uppercasePref = persistedConfig.getUppercasePriority();
|
||||
} catch (Exception ignore) {}
|
||||
|
||||
boolean numericEnabled = true;
|
||||
try {
|
||||
if (persistedConfig != null) numericEnabled = persistedConfig.getNumericSortEnabled();
|
||||
} catch (Exception ignore) {}
|
||||
|
||||
if (numericEnabled) {
|
||||
return naturalCompareWithUppercasePreference(s1, s2, uppercasePref);
|
||||
}
|
||||
|
||||
int ci = s1.compareToIgnoreCase(s2);
|
||||
if (ci != 0) return ci;
|
||||
|
||||
if (!uppercasePref) {
|
||||
return s1.compareTo(s2);
|
||||
}
|
||||
|
||||
int len = Math.min(s1.length(), s2.length());
|
||||
for (int k = 0; k < len; k++) {
|
||||
char aChar = s1.charAt(k);
|
||||
char bChar = s2.charAt(k);
|
||||
if (aChar == bChar) continue;
|
||||
char la = Character.toLowerCase(aChar);
|
||||
char lb = Character.toLowerCase(bChar);
|
||||
if (la != lb) return la - lb;
|
||||
boolean ua = Character.isUpperCase(aChar);
|
||||
boolean ub = Character.isUpperCase(bChar);
|
||||
if (ua != ub) return ua ? -1 : 1;
|
||||
}
|
||||
return s1.length() - s2.length();
|
||||
}
|
||||
|
||||
private int compareByColumnWithConfig(FileItem a, FileItem b, int column, boolean asc) {
|
||||
boolean da = a.isDirectory();
|
||||
boolean db = b.isDirectory();
|
||||
if (da != db) return da ? -1 : 1;
|
||||
|
||||
int valueCmp;
|
||||
switch (column) {
|
||||
case 1:
|
||||
valueCmp = Long.compare(a.getSize(), b.getSize());
|
||||
break;
|
||||
case 2:
|
||||
valueCmp = Long.compare(a.getModified().getTime(), b.getModified().getTime());
|
||||
break;
|
||||
default:
|
||||
valueCmp = compareNamesWithConfiguredOptions(a, b);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!asc) valueCmp = -valueCmp;
|
||||
if (valueCmp != 0) return valueCmp;
|
||||
|
||||
return compareNamesWithConfiguredOptions(a, b);
|
||||
}
|
||||
|
||||
private void sortItemsByColumn(int column, boolean asc) {
|
||||
@ -3259,57 +3380,16 @@ public class FilePanelTab extends JPanel {
|
||||
parentDir = items.remove(0);
|
||||
}
|
||||
|
||||
// Separate directories and files
|
||||
java.util.List<FileItem> directories = new ArrayList<>();
|
||||
java.util.List<FileItem> files = new ArrayList<>();
|
||||
|
||||
for (FileItem item : items) {
|
||||
if (item.isDirectory()) {
|
||||
directories.add(item);
|
||||
} else {
|
||||
files.add(item);
|
||||
}
|
||||
}
|
||||
java.util.Comparator<FileItem> comp = (a, b) -> compareByColumnWithConfig(a, b, column, asc);
|
||||
java.util.List<FileItem> sorted = new ArrayList<>(items);
|
||||
sorted.sort(comp);
|
||||
|
||||
java.util.Comparator<FileItem> comp;
|
||||
switch (column) {
|
||||
case 1: // size
|
||||
comp = (a, b) -> {
|
||||
int r = Long.compare(a.getSize(), b.getSize());
|
||||
if (r == 0) r = getCleanNameForSorting(a.getName()).compareToIgnoreCase(getCleanNameForSorting(b.getName()));
|
||||
return r;
|
||||
};
|
||||
break;
|
||||
case 2: // date
|
||||
// Sort by modification time
|
||||
comp = (a, b) -> {
|
||||
int r = Long.compare(a.getModified().getTime(), b.getModified().getTime());
|
||||
if (r == 0) r = getCleanNameForSorting(a.getName()).compareToIgnoreCase(getCleanNameForSorting(b.getName()));
|
||||
return r;
|
||||
};
|
||||
break;
|
||||
default: // name
|
||||
comp = (a, b) -> {
|
||||
String s1 = getCleanNameForSorting(a.getName());
|
||||
String s2 = getCleanNameForSorting(b.getName());
|
||||
return s1.compareToIgnoreCase(s2);
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
if (!asc) comp = comp.reversed();
|
||||
|
||||
// Sort both lists separately
|
||||
directories.sort(comp);
|
||||
files.sort(comp);
|
||||
|
||||
// Clear and rebuild items list: directories first, then files
|
||||
// Rebuild items list and keep parent directory entry at top.
|
||||
items.clear();
|
||||
if (parentDir != null) {
|
||||
items.add(parentDir);
|
||||
}
|
||||
items.addAll(directories);
|
||||
items.addAll(files);
|
||||
items.addAll(sorted);
|
||||
|
||||
// Refresh table on EDT
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
@ -3345,79 +3425,10 @@ public class FilePanelTab extends JPanel {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two FileItem objects by name with the following enhancements:
|
||||
* - directories come before files
|
||||
* - visible files come before hidden files
|
||||
* - natural numeric-aware comparison ("file2" < "file10")
|
||||
* - when names are equal ignoring case, prefer uppercase letters earlier (so "Apple" before "apple")
|
||||
*/
|
||||
private int compareFileItemsByName(FileItem a, FileItem b) {
|
||||
if (a == b) return 0;
|
||||
// Directories first
|
||||
boolean da = a.isDirectory(), db = b.isDirectory();
|
||||
if (da != db) return da ? -1 : 1;
|
||||
|
||||
// Hidden files ordering based on config (default: hidden last)
|
||||
boolean hiddenLast = true;
|
||||
try {
|
||||
if (persistedConfig != null) hiddenLast = persistedConfig.getHiddenFilesLast();
|
||||
} catch (Exception ignore) {}
|
||||
try {
|
||||
boolean ha = a.getFile() != null && a.getFile().isHidden();
|
||||
boolean hb = b.getFile() != null && b.getFile().isHidden();
|
||||
if (ha != hb) return hiddenLast ? (ha ? 1 : -1) : (ha ? -1 : 1);
|
||||
} catch (Exception ignore) {}
|
||||
|
||||
String s1 = a.getName() != null ? a.getName() : "";
|
||||
String s2 = b.getName() != null ? b.getName() : "";
|
||||
|
||||
// Optionally ignore leading dots (treat ".name" as "name") based on config
|
||||
try {
|
||||
if (persistedConfig != null && persistedConfig.getIgnoreLeadingDot()) {
|
||||
s1 = s1.replaceFirst("^\\.+", "");
|
||||
s2 = s2.replaceFirst("^\\.+", "");
|
||||
}
|
||||
} catch (Exception ignore) {}
|
||||
|
||||
// Numeric-aware vs simple compare based on config
|
||||
boolean numericEnabled = true;
|
||||
try { if (persistedConfig != null) numericEnabled = persistedConfig.getNumericSortEnabled(); } catch (Exception ignore) {}
|
||||
|
||||
if (numericEnabled) {
|
||||
// Use natural compare; uppercase preference handled inside
|
||||
return naturalCompareWithUppercasePreference(s1, s2);
|
||||
} else {
|
||||
// simple case-insensitive compare, optionally uppercase preference
|
||||
int ci = s1.compareToIgnoreCase(s2);
|
||||
if (ci != 0) return ci;
|
||||
boolean uppercasePref = true;
|
||||
try { if (persistedConfig != null) uppercasePref = persistedConfig.getUppercasePriority(); } catch (Exception ignore) {}
|
||||
if (uppercasePref) {
|
||||
// prefer uppercase earlier
|
||||
int len = Math.min(s1.length(), s2.length());
|
||||
for (int k = 0; k < len; k++) {
|
||||
char aChar = s1.charAt(k);
|
||||
char bChar = s2.charAt(k);
|
||||
if (aChar == bChar) continue;
|
||||
char la = Character.toLowerCase(aChar);
|
||||
char lb = Character.toLowerCase(bChar);
|
||||
if (la != lb) return la - lb;
|
||||
boolean ua = Character.isUpperCase(aChar);
|
||||
boolean ub = Character.isUpperCase(bChar);
|
||||
if (ua != ub) return ua ? -1 : 1;
|
||||
}
|
||||
return s1.length() - s2.length();
|
||||
} else {
|
||||
return s1.compareTo(s2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Natural compare of two strings with numeric awareness and uppercase preference.
|
||||
*/
|
||||
private int naturalCompareWithUppercasePreference(String s1, String s2) {
|
||||
private int naturalCompareWithUppercasePreference(String s1, String s2, boolean uppercasePref) {
|
||||
int i = 0, j = 0;
|
||||
int n1 = s1.length(), n2 = s2.length();
|
||||
|
||||
@ -3460,7 +3471,7 @@ public class FilePanelTab extends JPanel {
|
||||
String part1 = sb1.toString();
|
||||
String part2 = sb2.toString();
|
||||
|
||||
int ci = compareStringPartWithUppercasePreference(part1, part2);
|
||||
int ci = compareStringPartWithUppercasePreference(part1, part2, uppercasePref);
|
||||
if (ci != 0) return ci;
|
||||
|
||||
i = ti;
|
||||
@ -3477,9 +3488,12 @@ public class FilePanelTab extends JPanel {
|
||||
* Compare two non-digit string parts: case-insensitive, but if equal ignoring case,
|
||||
* prefer the one with uppercase letters earlier.
|
||||
*/
|
||||
private int compareStringPartWithUppercasePreference(String p1, String p2) {
|
||||
private int compareStringPartWithUppercasePreference(String p1, String p2, boolean uppercasePref) {
|
||||
int ci = p1.compareToIgnoreCase(p2);
|
||||
if (ci != 0) return ci;
|
||||
if (!uppercasePref) {
|
||||
return p1.compareTo(p2);
|
||||
}
|
||||
// If equal ignoring case, prefer uppercase earlier
|
||||
int len = Math.min(p1.length(), p2.length());
|
||||
for (int k = 0; k < len; k++) {
|
||||
@ -3530,26 +3544,20 @@ public class FilePanelTab extends JPanel {
|
||||
public void setAppConfig(cz.kamma.kfmanager.config.AppConfig cfg) {
|
||||
this.persistedConfig = cfg;
|
||||
iconCache.clear();
|
||||
|
||||
// Apply persisted sort if present - try global.sort.column first, then MultipleSortCriteria
|
||||
|
||||
// Apply persisted sort settings from Sorting configuration.
|
||||
if (cfg != null) {
|
||||
int col = cfg.getDefaultSortColumn();
|
||||
boolean asc = cfg.getDefaultSortAscending();
|
||||
|
||||
if (col >= 0) {
|
||||
// Use global sort column setting
|
||||
this.sortColumn = col;
|
||||
this.sortAscending = asc;
|
||||
// If items are already loaded, sort them
|
||||
if (tableModel != null && tableModel.items != null && !tableModel.items.isEmpty()) {
|
||||
sortItemsByColumn(sortColumn, sortAscending);
|
||||
}
|
||||
} else {
|
||||
// No global sort setting, try multiple criteria
|
||||
java.util.List<String> multi = cfg.getMultipleSortCriteria();
|
||||
if (multi != null && !multi.isEmpty()) {
|
||||
applyMultipleSortCriteria(multi);
|
||||
}
|
||||
|
||||
if (col < 0 || col > 2) {
|
||||
col = 0;
|
||||
}
|
||||
this.sortColumn = col;
|
||||
this.sortAscending = asc;
|
||||
|
||||
if (tableModel != null && tableModel.items != null && !tableModel.items.isEmpty()) {
|
||||
sortItemsByColumn(sortColumn, sortAscending);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3557,74 +3565,6 @@ public class FilePanelTab extends JPanel {
|
||||
updateSortButtonsDisplay();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a list of composite sort criteria in order (e.g. "name:asc", "size:desc").
|
||||
*/
|
||||
private void applyMultipleSortCriteria(java.util.List<String> criteria) {
|
||||
if (criteria == null || criteria.isEmpty()) return;
|
||||
if (tableModel == null || tableModel.items == null) return;
|
||||
|
||||
java.util.Comparator<FileItem> comp = null;
|
||||
|
||||
for (String c : criteria) {
|
||||
if (c == null || c.trim().isEmpty()) continue;
|
||||
String[] parts = c.split(":");
|
||||
String field = parts[0].trim().toLowerCase();
|
||||
boolean asc = true;
|
||||
if (parts.length > 1 && parts[1].trim().equalsIgnoreCase("desc")) asc = false;
|
||||
|
||||
java.util.Comparator<FileItem> partComp;
|
||||
switch (field) {
|
||||
case "size":
|
||||
partComp = (a, b) -> {
|
||||
boolean da = a.isDirectory(), db = b.isDirectory();
|
||||
if (da != db) return da ? -1 : 1;
|
||||
if (da && db) return a.getName().compareToIgnoreCase(b.getName());
|
||||
int r = Long.compare(a.getSize(), b.getSize());
|
||||
if (r == 0) r = a.getName().compareToIgnoreCase(b.getName());
|
||||
return r;
|
||||
};
|
||||
break;
|
||||
case "date":
|
||||
partComp = (a, b) -> {
|
||||
int r = Long.compare(a.getModified().getTime(), b.getModified().getTime());
|
||||
if (r == 0) r = a.getName().compareToIgnoreCase(b.getName());
|
||||
return r;
|
||||
};
|
||||
break;
|
||||
default:
|
||||
partComp = (a, b) -> {
|
||||
boolean da = a.isDirectory(), db = b.isDirectory();
|
||||
if (da != db) return da ? -1 : 1;
|
||||
return a.getName().compareToIgnoreCase(b.getName());
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
if (!asc) partComp = partComp.reversed();
|
||||
|
||||
if (comp == null) comp = partComp;
|
||||
else comp = comp.thenComparing(partComp);
|
||||
}
|
||||
|
||||
if (comp == null) return;
|
||||
|
||||
java.util.List<FileItem> items = tableModel.items;
|
||||
items.sort(comp);
|
||||
|
||||
SwingUtilities.invokeLater(() -> {
|
||||
if (viewMode == ViewMode.BRIEF) {
|
||||
tableModel.calculateBriefLayout();
|
||||
tableModel.fireTableStructureChanged();
|
||||
} else {
|
||||
tableModel.fireTableDataChanged();
|
||||
}
|
||||
updateColumnRenderers();
|
||||
updateColumnWidths();
|
||||
if (fileTable.getTableHeader() != null) fileTable.getTableHeader().repaint();
|
||||
});
|
||||
}
|
||||
|
||||
// Getters
|
||||
public JTable getFileTable() {
|
||||
return fileTable;
|
||||
|
||||
@ -269,12 +269,18 @@ public class MainWindow extends JFrame {
|
||||
Component focused = (Component) evt.getNewValue();
|
||||
if (focused != null) {
|
||||
if (SwingUtilities.isDescendingFrom(focused, leftPanel)) {
|
||||
if (activePanel != leftPanel && leftPanel.isNonActivatingFocusOwner(focused)) {
|
||||
return;
|
||||
}
|
||||
activePanel = leftPanel;
|
||||
updateActivePanelBorder();
|
||||
leftPanel.getFileTable().repaint();
|
||||
rightPanel.getFileTable().repaint();
|
||||
updateCommandLinePrompt();
|
||||
} else if (SwingUtilities.isDescendingFrom(focused, rightPanel)) {
|
||||
if (activePanel != rightPanel && rightPanel.isNonActivatingFocusOwner(focused)) {
|
||||
return;
|
||||
}
|
||||
activePanel = rightPanel;
|
||||
updateActivePanelBorder();
|
||||
leftPanel.getFileTable().repaint();
|
||||
|
||||
@ -166,49 +166,31 @@ public class SettingsDialog extends JDialog {
|
||||
JPanel sortingHolder = (JPanel) panels.get("Sorting");
|
||||
if (sortingHolder != null) {
|
||||
try {
|
||||
JComboBox<?> pf = (JComboBox<?>) sortingHolder.getClientProperty("primaryField");
|
||||
JComboBox<?> po = (JComboBox<?>) sortingHolder.getClientProperty("primaryOrder");
|
||||
JComboBox<?> sf = (JComboBox<?>) sortingHolder.getClientProperty("secondaryField");
|
||||
JComboBox<?> so = (JComboBox<?>) sortingHolder.getClientProperty("secondaryOrder");
|
||||
JComboBox<?> tf = (JComboBox<?>) sortingHolder.getClientProperty("tertiaryField");
|
||||
JComboBox<?> to = (JComboBox<?>) sortingHolder.getClientProperty("tertiaryOrder");
|
||||
JComboBox<?> sortField = (JComboBox<?>) sortingHolder.getClientProperty("sortField");
|
||||
JComboBox<?> sortOrder = (JComboBox<?>) sortingHolder.getClientProperty("sortOrder");
|
||||
|
||||
java.util.List<String> criteria = new java.util.ArrayList<>();
|
||||
|
||||
if (pf != null && po != null) {
|
||||
String f = pf.getSelectedItem() != null ? pf.getSelectedItem().toString().toLowerCase() : "";
|
||||
String ord = po.getSelectedItem() != null && po.getSelectedItem().toString().equalsIgnoreCase("Descending") ? "desc" : "asc";
|
||||
if (!f.isEmpty()) criteria.add(f + ":" + ord);
|
||||
}
|
||||
|
||||
if (sf != null && so != null) {
|
||||
String s = sf.getSelectedItem() != null ? sf.getSelectedItem().toString() : "(none)";
|
||||
if (!"(none)".equals(s)) {
|
||||
String field = s.toLowerCase();
|
||||
String ord = so.getSelectedItem() != null && so.getSelectedItem().toString().equalsIgnoreCase("Descending") ? "desc" : "asc";
|
||||
criteria.add(field + ":" + ord);
|
||||
int sortColumn = 0;
|
||||
if (sortField != null && sortField.getSelectedItem() != null) {
|
||||
String selected = sortField.getSelectedItem().toString();
|
||||
if ("Size".equals(selected)) {
|
||||
sortColumn = 1;
|
||||
} else if ("Date".equals(selected)) {
|
||||
sortColumn = 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (tf != null && to != null) {
|
||||
String t = tf.getSelectedItem() != null ? tf.getSelectedItem().toString() : "(none)";
|
||||
if (!"(none)".equals(t)) {
|
||||
String field = t.toLowerCase();
|
||||
String ord = to.getSelectedItem() != null && to.getSelectedItem().toString().equalsIgnoreCase("Descending") ? "desc" : "asc";
|
||||
criteria.add(field + ":" + ord);
|
||||
}
|
||||
}
|
||||
boolean ascending = sortOrder == null
|
||||
|| sortOrder.getSelectedItem() == null
|
||||
|| "Ascending".equals(sortOrder.getSelectedItem().toString());
|
||||
|
||||
config.setMultipleSortCriteria(criteria);
|
||||
config.setDefaultSortColumn(sortColumn);
|
||||
config.setDefaultSortAscending(ascending);
|
||||
// Legacy multi-sort criteria are no longer configurable from UI.
|
||||
config.setMultipleSortCriteria(java.util.Collections.emptyList());
|
||||
|
||||
// save extra sorting options
|
||||
JComboBox<?> hiddenOrder = (JComboBox<?>) sortingHolder.getClientProperty("hiddenOrder");
|
||||
JCheckBox uppercasePriority = (JCheckBox) sortingHolder.getClientProperty("uppercasePriority");
|
||||
JCheckBox numericAware = (JCheckBox) sortingHolder.getClientProperty("numericAware");
|
||||
if (hiddenOrder != null) {
|
||||
boolean hiddenLast = "Hidden last".equals(hiddenOrder.getSelectedItem());
|
||||
config.setHiddenFilesLast(hiddenLast);
|
||||
}
|
||||
if (uppercasePriority != null) {
|
||||
config.setUppercasePriority(uppercasePriority.isSelected());
|
||||
}
|
||||
@ -219,6 +201,10 @@ public class SettingsDialog extends JDialog {
|
||||
if (ignoreLeadingDot != null) {
|
||||
config.setIgnoreLeadingDot(ignoreLeadingDot.isSelected());
|
||||
}
|
||||
JCheckBox ignoreLeadingDollar = (JCheckBox) sortingHolder.getClientProperty("ignoreLeadingDollar");
|
||||
if (ignoreLeadingDollar != null) {
|
||||
config.setIgnoreLeadingDollar(ignoreLeadingDollar.isSelected());
|
||||
}
|
||||
} catch (Exception ignore) {}
|
||||
}
|
||||
|
||||
@ -283,7 +269,6 @@ public class SettingsDialog extends JDialog {
|
||||
config.setSelectionColor(originalSel);
|
||||
config.setMarkedColor(originalMark);
|
||||
config.setFolderColor(originalFolder);
|
||||
config.setBriefModeMaxNameLength(originalBriefMaxLen);
|
||||
config.setBriefModeStartLength(originalBriefStartLen);
|
||||
config.setBriefModeEndLength(originalBriefEndLen);
|
||||
config.setBriefModeSeparator(originalBriefSeparator);
|
||||
@ -463,15 +448,6 @@ public class SettingsDialog extends JDialog {
|
||||
gbc.gridx = 0; gbc.gridy = row; gbc.weightx = 0.0;
|
||||
grid.add(new JLabel("Brief mode max name length:"), gbc);
|
||||
|
||||
JSpinner briefMaxLenSpinner = new JSpinner(new SpinnerNumberModel(config.getBriefModeMaxNameLength(), 10, 255, 1));
|
||||
briefMaxLenSpinner.addChangeListener(e -> {
|
||||
config.setBriefModeMaxNameLength((Integer) briefMaxLenSpinner.getValue());
|
||||
if (onChange != null) onChange.run();
|
||||
});
|
||||
p.putClientProperty("briefMaxLen", briefMaxLenSpinner);
|
||||
gbc.gridx = 1; gbc.gridy = row++; gbc.weightx = 1.0;
|
||||
grid.add(briefMaxLenSpinner, gbc);
|
||||
|
||||
// Brief mode start/end length
|
||||
gbc.gridx = 0; gbc.gridy = row; gbc.weightx = 0.0;
|
||||
grid.add(new JLabel("Brief mode truncation (start/end):"), gbc);
|
||||
@ -665,31 +641,14 @@ public class SettingsDialog extends JDialog {
|
||||
grid.add(Box.createVerticalStrut(8));
|
||||
};
|
||||
|
||||
JComboBox<String> primaryField = new JComboBox<>(new String[]{"Name", "Size", "Date"});
|
||||
JComboBox<String> primaryOrder = new JComboBox<>(new String[]{"Ascending", "Descending"});
|
||||
JPanel primaryPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0));
|
||||
primaryPanel.add(primaryField);
|
||||
primaryPanel.add(primaryOrder);
|
||||
addLabeled.accept("Primary sort:", primaryPanel);
|
||||
|
||||
JComboBox<String> secondaryField = new JComboBox<>(new String[]{"(none)", "Name", "Size", "Date"});
|
||||
JComboBox<String> secondaryOrder = new JComboBox<>(new String[]{"Ascending", "Descending"});
|
||||
JPanel secondaryPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0));
|
||||
secondaryPanel.add(secondaryField);
|
||||
secondaryPanel.add(secondaryOrder);
|
||||
addLabeled.accept("Secondary sort:", secondaryPanel);
|
||||
|
||||
JComboBox<String> tertiaryField = new JComboBox<>(new String[]{"(none)", "Name", "Size", "Date"});
|
||||
JComboBox<String> tertiaryOrder = new JComboBox<>(new String[]{"Ascending", "Descending"});
|
||||
JPanel tertiaryPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0));
|
||||
tertiaryPanel.add(tertiaryField);
|
||||
tertiaryPanel.add(tertiaryOrder);
|
||||
addLabeled.accept("Tertiary sort:", tertiaryPanel);
|
||||
JComboBox<String> sortField = new JComboBox<>(new String[]{"Name", "Size", "Date"});
|
||||
JComboBox<String> sortOrder = new JComboBox<>(new String[]{"Ascending", "Descending"});
|
||||
JPanel sortPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 6, 0));
|
||||
sortPanel.add(sortField);
|
||||
sortPanel.add(sortOrder);
|
||||
addLabeled.accept("Sort by:", sortPanel);
|
||||
|
||||
// Additional sorting options (each as own labeled row)
|
||||
JComboBox<String> hiddenOrder = new JComboBox<>(new String[]{"Hidden last", "Hidden first"});
|
||||
hiddenOrder.setSelectedIndex(config.getHiddenFilesLast() ? 0 : 1);
|
||||
addLabeled.accept("Hidden files:", hiddenOrder);
|
||||
|
||||
JCheckBox uppercasePriority = new JCheckBox("Prefer uppercase first");
|
||||
uppercasePriority.setSelected(config.getUppercasePriority());
|
||||
@ -703,43 +662,31 @@ public class SettingsDialog extends JDialog {
|
||||
ignoreLeadingDot.setSelected(config.getIgnoreLeadingDot());
|
||||
addLabeled.accept("Ignore leading dot:", ignoreLeadingDot);
|
||||
|
||||
// Load existing criteria from config
|
||||
java.util.List<String> crit = config.getMultipleSortCriteria();
|
||||
if (crit != null && !crit.isEmpty()) {
|
||||
try {
|
||||
// parse strings like "name:asc"
|
||||
if (crit.size() > 0) {
|
||||
String[] parts = crit.getFirst().split(":");
|
||||
if (parts.length >= 1) primaryField.setSelectedItem(capitalize(parts[0]));
|
||||
if (parts.length == 2 && parts[1].equalsIgnoreCase("desc")) primaryOrder.setSelectedItem("Descending");
|
||||
}
|
||||
if (crit.size() > 1) {
|
||||
String[] parts = crit.get(1).split(":");
|
||||
if (parts.length >= 1) secondaryField.setSelectedItem("(" + parts[0] + ")");
|
||||
if (parts.length == 2 && parts[1].equalsIgnoreCase("desc")) secondaryOrder.setSelectedItem("Descending");
|
||||
}
|
||||
if (crit.size() > 2) {
|
||||
String[] parts = crit.get(2).split(":");
|
||||
if (parts.length >= 1) tertiaryField.setSelectedItem("(" + parts[0] + ")");
|
||||
if (parts.length == 2 && parts[1].equalsIgnoreCase("desc")) tertiaryOrder.setSelectedItem("Descending");
|
||||
}
|
||||
} catch (Exception ignore) {}
|
||||
JCheckBox ignoreLeadingDollar = new JCheckBox("Ignore leading $ in names (treat '$name' as 'name')");
|
||||
ignoreLeadingDollar.setSelected(config.getIgnoreLeadingDollar());
|
||||
addLabeled.accept("Ignore leading $:", ignoreLeadingDollar);
|
||||
|
||||
int defaultColumn = config.getDefaultSortColumn();
|
||||
boolean defaultAsc = config.getDefaultSortAscending();
|
||||
if (defaultColumn == 1) {
|
||||
sortField.setSelectedItem("Size");
|
||||
} else if (defaultColumn == 2) {
|
||||
sortField.setSelectedItem("Date");
|
||||
} else {
|
||||
sortField.setSelectedItem("Name");
|
||||
}
|
||||
sortOrder.setSelectedItem(defaultAsc ? "Ascending" : "Descending");
|
||||
|
||||
p.add(grid, BorderLayout.NORTH);
|
||||
|
||||
// Save action will be done on OK; store controls in panels map so OK handler can read them
|
||||
JPanel holder = new JPanel();
|
||||
holder.putClientProperty("primaryField", primaryField);
|
||||
holder.putClientProperty("primaryOrder", primaryOrder);
|
||||
holder.putClientProperty("secondaryField", secondaryField);
|
||||
holder.putClientProperty("secondaryOrder", secondaryOrder);
|
||||
holder.putClientProperty("tertiaryField", tertiaryField);
|
||||
holder.putClientProperty("tertiaryOrder", tertiaryOrder);
|
||||
holder.putClientProperty("hiddenOrder", hiddenOrder);
|
||||
holder.putClientProperty("sortField", sortField);
|
||||
holder.putClientProperty("sortOrder", sortOrder);
|
||||
holder.putClientProperty("uppercasePriority", uppercasePriority);
|
||||
holder.putClientProperty("numericAware", numericAware);
|
||||
holder.putClientProperty("ignoreLeadingDot", ignoreLeadingDot);
|
||||
holder.putClientProperty("ignoreLeadingDollar", ignoreLeadingDollar);
|
||||
panels.put("Sorting", holder);
|
||||
|
||||
return p;
|
||||
@ -963,11 +910,6 @@ public class SettingsDialog extends JDialog {
|
||||
return p;
|
||||
}
|
||||
|
||||
private static String capitalize(String s) {
|
||||
if (s == null || s.isEmpty()) return s;
|
||||
return s.substring(0,1).toUpperCase() + s.substring(1).toLowerCase();
|
||||
}
|
||||
|
||||
private String getFontDescription(Font f) {
|
||||
if (f == null) return "(default)";
|
||||
return "%s %dpt".formatted(f.getName(), f.getSize());
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user