added sorting, not working
This commit is contained in:
parent
7529cb31eb
commit
5a7312eb8e
@ -321,6 +321,72 @@ public class AppConfig {
|
|||||||
public void setDefaultSortAscending(boolean asc) {
|
public void setDefaultSortAscending(boolean asc) {
|
||||||
properties.setProperty("global.sort.ascending", String.valueOf(asc));
|
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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUppercasePriority(boolean priority) {
|
||||||
|
properties.setProperty("global.sort.uppercase.priority", String.valueOf(priority));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numeric-aware name sorting (natural sort): default true
|
||||||
|
public boolean getNumericSortEnabled() {
|
||||||
|
return Boolean.parseBoolean(properties.getProperty("global.sort.numeric.enabled", "true"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNumericSortEnabled(boolean enabled) {
|
||||||
|
properties.setProperty("global.sort.numeric.enabled", String.valueOf(enabled));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore leading dot in names when sorting (treat ".name" as "name")
|
||||||
|
public boolean getIgnoreLeadingDot() {
|
||||||
|
return Boolean.parseBoolean(properties.getProperty("global.sort.ignore.leadingdot", "false"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIgnoreLeadingDot(boolean enabled) {
|
||||||
|
properties.setProperty("global.sort.ignore.leadingdot", String.valueOf(enabled));
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Multiple sort criteria persistence
|
||||||
|
public java.util.List<String> getMultipleSortCriteria() {
|
||||||
|
java.util.List<String> list = new java.util.ArrayList<>();
|
||||||
|
int count = Integer.parseInt(properties.getProperty("global.sort.criteria.count", "0"));
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
String v = properties.getProperty("global.sort.criteria." + i, null);
|
||||||
|
if (v != null && !v.isEmpty()) list.add(v);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMultipleSortCriteria(java.util.List<String> criteria) {
|
||||||
|
if (criteria == null || criteria.isEmpty()) {
|
||||||
|
properties.setProperty("global.sort.criteria.count", "0");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int limit = Math.min(criteria.size(), 5); // cap stored entries
|
||||||
|
properties.setProperty("global.sort.criteria.count", String.valueOf(limit));
|
||||||
|
for (int i = 0; i < limit; i++) {
|
||||||
|
properties.setProperty("global.sort.criteria." + i, criteria.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove any old entries beyond limit
|
||||||
|
int old = Integer.parseInt(properties.getProperty("global.sort.criteria.count", "0"));
|
||||||
|
for (int i = limit; i < old; i++) {
|
||||||
|
properties.remove("global.sort.criteria." + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save window state
|
* Save window state
|
||||||
|
|||||||
@ -1247,8 +1247,8 @@ public class FilePanelTab extends JPanel {
|
|||||||
} else {
|
} else {
|
||||||
// Preserve whatever style the base font has (do not force plain)
|
// Preserve whatever style the base font has (do not force plain)
|
||||||
setFont(baseFont.deriveFont(baseStyle));
|
setFont(baseFont.deriveFont(baseStyle));
|
||||||
// Use a light foreground color for better contrast on dark backgrounds
|
// Use a white foreground color for better contrast on dark backgrounds
|
||||||
setForeground(new Color(240, 240, 240));
|
setForeground(new Color(255, 255, 255));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Zobrazit ikonu pro názvy souborů
|
// Zobrazit ikonu pro názvy souborů
|
||||||
@ -1414,7 +1414,7 @@ public class FilePanelTab extends JPanel {
|
|||||||
java.util.List<FileItem> items = tableModel.items;
|
java.util.List<FileItem> items = tableModel.items;
|
||||||
if (items.isEmpty()) return;
|
if (items.isEmpty()) return;
|
||||||
|
|
||||||
java.util.Comparator<FileItem> comp;
|
java.util.Comparator<FileItem> comp;
|
||||||
switch (column) {
|
switch (column) {
|
||||||
case 1: // size
|
case 1: // size
|
||||||
comp = (a, b) -> {
|
comp = (a, b) -> {
|
||||||
@ -1435,11 +1435,7 @@ public class FilePanelTab extends JPanel {
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
default: // name
|
default: // name
|
||||||
comp = (a, b) -> {
|
comp = (a, b) -> compareFileItemsByName(a, b);
|
||||||
boolean da = a.isDirectory(), db = b.isDirectory();
|
|
||||||
if (da != db) return da ? -1 : 1;
|
|
||||||
return a.getName().compareToIgnoreCase(b.getName());
|
|
||||||
};
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1461,6 +1457,159 @@ 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) {
|
||||||
|
int i = 0, j = 0;
|
||||||
|
int n1 = s1.length(), n2 = s2.length();
|
||||||
|
|
||||||
|
while (i < n1 && j < n2) {
|
||||||
|
char c1 = s1.charAt(i);
|
||||||
|
char c2 = s2.charAt(j);
|
||||||
|
|
||||||
|
// If both are digits, compare whole number sequences numerically
|
||||||
|
if (Character.isDigit(c1) && Character.isDigit(c2)) {
|
||||||
|
int start1 = i, start2 = j;
|
||||||
|
while (i < n1 && Character.isDigit(s1.charAt(i))) i++;
|
||||||
|
while (j < n2 && Character.isDigit(s2.charAt(j))) j++;
|
||||||
|
String num1 = s1.substring(start1, i);
|
||||||
|
String num2 = s2.substring(start2, j);
|
||||||
|
|
||||||
|
// Remove leading zeros for numeric comparison, but keep length for tie-break
|
||||||
|
String nz1 = num1.replaceFirst("^0+(?!$)", "");
|
||||||
|
String nz2 = num2.replaceFirst("^0+(?!$)", "");
|
||||||
|
if (nz1.length() != nz2.length()) return nz1.length() - nz2.length();
|
||||||
|
int cmp = nz1.compareTo(nz2);
|
||||||
|
if (cmp != 0) return cmp;
|
||||||
|
// If equal numerically, shorter original with fewer leading zeros first
|
||||||
|
if (num1.length() != num2.length()) return num1.length() - num2.length();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-digit comparison: case-insensitive first
|
||||||
|
StringBuilder sb1 = new StringBuilder();
|
||||||
|
StringBuilder sb2 = new StringBuilder();
|
||||||
|
int ti = i, tj = j;
|
||||||
|
while (ti < n1 && !Character.isDigit(s1.charAt(ti))) {
|
||||||
|
sb1.append(s1.charAt(ti));
|
||||||
|
ti++;
|
||||||
|
}
|
||||||
|
while (tj < n2 && !Character.isDigit(s2.charAt(tj))) {
|
||||||
|
sb2.append(s2.charAt(tj));
|
||||||
|
tj++;
|
||||||
|
}
|
||||||
|
|
||||||
|
String part1 = sb1.toString();
|
||||||
|
String part2 = sb2.toString();
|
||||||
|
|
||||||
|
int ci = compareStringPartWithUppercasePreference(part1, part2);
|
||||||
|
if (ci != 0) return ci;
|
||||||
|
|
||||||
|
i = ti;
|
||||||
|
j = tj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If one string ended earlier, shorter one is first
|
||||||
|
if (i < n1) return 1; // s2 ended, s1 longer -> s2 first
|
||||||
|
if (j < n2) return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
int ci = p1.compareToIgnoreCase(p2);
|
||||||
|
if (ci != 0) return ci;
|
||||||
|
// If equal ignoring case, prefer uppercase earlier
|
||||||
|
int len = Math.min(p1.length(), p2.length());
|
||||||
|
for (int k = 0; k < len; k++) {
|
||||||
|
char a = p1.charAt(k);
|
||||||
|
char b = p2.charAt(k);
|
||||||
|
if (a == b) continue;
|
||||||
|
char la = Character.toLowerCase(a);
|
||||||
|
char lb = Character.toLowerCase(b);
|
||||||
|
if (la != lb) return la - lb; // different letters
|
||||||
|
// same letter ignoring case, decide by uppercase preference
|
||||||
|
boolean ua = Character.isUpperCase(a);
|
||||||
|
boolean ub = Character.isUpperCase(b);
|
||||||
|
if (ua != ub) return ua ? -1 : 1; // uppercase comes first
|
||||||
|
}
|
||||||
|
// If equal up to min length, shorter one first
|
||||||
|
return p1.length() - p2.length();
|
||||||
|
}
|
||||||
|
|
||||||
/** Header renderer that decorates the column title with an arrow for the sort column/direction */
|
/** Header renderer that decorates the column title with an arrow for the sort column/direction */
|
||||||
private class SortHeaderRenderer implements javax.swing.table.TableCellRenderer {
|
private class SortHeaderRenderer implements javax.swing.table.TableCellRenderer {
|
||||||
private final javax.swing.table.TableCellRenderer delegate;
|
private final javax.swing.table.TableCellRenderer delegate;
|
||||||
@ -1495,19 +1644,92 @@ public class FilePanelTab extends JPanel {
|
|||||||
this.persistedConfig = cfg;
|
this.persistedConfig = cfg;
|
||||||
// Apply persisted sort if present
|
// Apply persisted sort if present
|
||||||
if (cfg != null) {
|
if (cfg != null) {
|
||||||
int col = cfg.getDefaultSortColumn();
|
java.util.List<String> multi = cfg.getMultipleSortCriteria();
|
||||||
boolean asc = cfg.getDefaultSortAscending();
|
if (multi != null && !multi.isEmpty()) {
|
||||||
if (col >= 0) {
|
applyMultipleSortCriteria(multi);
|
||||||
this.sortColumn = col;
|
} else {
|
||||||
this.sortAscending = asc;
|
int col = cfg.getDefaultSortColumn();
|
||||||
sortItemsByColumn(sortColumn, sortAscending);
|
boolean asc = cfg.getDefaultSortAscending();
|
||||||
SwingUtilities.invokeLater(() -> {
|
if (col >= 0) {
|
||||||
tableModel.fireTableDataChanged();
|
this.sortColumn = col;
|
||||||
if (fileTable.getTableHeader() != null) fileTable.getTableHeader().repaint();
|
this.sortAscending = asc;
|
||||||
});
|
sortItemsByColumn(sortColumn, sortAscending);
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
tableModel.fireTableDataChanged();
|
||||||
|
if (fileTable.getTableHeader() != null) fileTable.getTableHeader().repaint();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private String formatSize(long size) {
|
private String formatSize(long size) {
|
||||||
if (size < 1024) {
|
if (size < 1024) {
|
||||||
|
|||||||
@ -390,6 +390,16 @@ public class MainWindow extends JFrame {
|
|||||||
if (leftPanel != null) leftPanel.applyMarkedColor(mark);
|
if (leftPanel != null) leftPanel.applyMarkedColor(mark);
|
||||||
if (rightPanel != null) rightPanel.applyMarkedColor(mark);
|
if (rightPanel != null) rightPanel.applyMarkedColor(mark);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-propagate AppConfig to panels so they can pick up sorting and other config changes
|
||||||
|
SwingUtilities.invokeLater(() -> {
|
||||||
|
try {
|
||||||
|
if (leftPanel != null) leftPanel.setAppConfig(config);
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
try {
|
||||||
|
if (rightPanel != null) rightPanel.setAppConfig(config);
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -40,9 +40,10 @@ public class SettingsDialog extends JDialog {
|
|||||||
setLocationRelativeTo(parent);
|
setLocationRelativeTo(parent);
|
||||||
|
|
||||||
// Left: categories
|
// Left: categories
|
||||||
DefaultListModel<String> model = new DefaultListModel<>();
|
DefaultListModel<String> model = new DefaultListModel<>();
|
||||||
model.addElement("Appearance");
|
model.addElement("Appearance");
|
||||||
model.addElement("Editor");
|
model.addElement("Editor");
|
||||||
|
model.addElement("Sorting");
|
||||||
categoryList = new JList<>(model);
|
categoryList = new JList<>(model);
|
||||||
categoryList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
categoryList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||||
categoryList.setSelectedIndex(0);
|
categoryList.setSelectedIndex(0);
|
||||||
@ -51,8 +52,9 @@ public class SettingsDialog extends JDialog {
|
|||||||
cards = new JPanel(cardLayout);
|
cards = new JPanel(cardLayout);
|
||||||
|
|
||||||
// Build category panels
|
// Build category panels
|
||||||
cards.add(buildAppearancePanel(), "Appearance");
|
cards.add(buildAppearancePanel(), "Appearance");
|
||||||
cards.add(buildEditorPanel(), "Editor");
|
cards.add(buildEditorPanel(), "Editor");
|
||||||
|
cards.add(buildSortingPanel(), "Sorting");
|
||||||
|
|
||||||
categoryList.addListSelectionListener(e -> {
|
categoryList.addListSelectionListener(e -> {
|
||||||
if (!e.getValueIsAdjusting()) {
|
if (!e.getValueIsAdjusting()) {
|
||||||
@ -71,8 +73,69 @@ public class SettingsDialog extends JDialog {
|
|||||||
JPanel btns = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
JPanel btns = new JPanel(new FlowLayout(FlowLayout.RIGHT));
|
||||||
JButton ok = new JButton("OK");
|
JButton ok = new JButton("OK");
|
||||||
ok.addActionListener(e -> {
|
ok.addActionListener(e -> {
|
||||||
// Persist is handled by individual actions; ensure saved and close
|
// Collect sorting settings from the Sorting panel (if present)
|
||||||
|
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");
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config.setMultipleSortCriteria(criteria);
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
}
|
||||||
|
if (numericAware != null) {
|
||||||
|
config.setNumericSortEnabled(numericAware.isSelected());
|
||||||
|
}
|
||||||
|
JCheckBox ignoreLeadingDot = (JCheckBox) sortingHolder.getClientProperty("ignoreLeadingDot");
|
||||||
|
if (ignoreLeadingDot != null) {
|
||||||
|
config.setIgnoreLeadingDot(ignoreLeadingDot.isSelected());
|
||||||
|
}
|
||||||
|
} catch (Exception ignore) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Persist config and notify caller
|
||||||
config.saveConfig();
|
config.saveConfig();
|
||||||
|
if (onChange != null) onChange.run();
|
||||||
dispose();
|
dispose();
|
||||||
});
|
});
|
||||||
JButton cancel = new JButton("Cancel");
|
JButton cancel = new JButton("Cancel");
|
||||||
@ -192,6 +255,112 @@ public class SettingsDialog extends JDialog {
|
|||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private JPanel buildSortingPanel() {
|
||||||
|
JPanel p = new JPanel(new BorderLayout(8, 8));
|
||||||
|
JPanel grid = new JPanel();
|
||||||
|
grid.setLayout(new BoxLayout(grid, BoxLayout.Y_AXIS));
|
||||||
|
grid.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
|
||||||
|
|
||||||
|
// Helper to add a label above a control for single-column layout
|
||||||
|
java.util.function.BiConsumer<String, JComponent> addLabeled = (labelText, comp) -> {
|
||||||
|
JPanel row = new JPanel();
|
||||||
|
row.setLayout(new BoxLayout(row, BoxLayout.Y_AXIS));
|
||||||
|
JLabel lbl = new JLabel(labelText);
|
||||||
|
lbl.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||||
|
comp.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||||
|
row.add(lbl);
|
||||||
|
row.add(Box.createVerticalStrut(4));
|
||||||
|
row.add(comp);
|
||||||
|
row.setAlignmentX(Component.LEFT_ALIGNMENT);
|
||||||
|
grid.add(row);
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 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());
|
||||||
|
addLabeled.accept("Uppercase priority:", uppercasePriority);
|
||||||
|
|
||||||
|
JCheckBox numericAware = new JCheckBox("Enable natural numeric sorting");
|
||||||
|
numericAware.setSelected(config.getNumericSortEnabled());
|
||||||
|
addLabeled.accept("Numeric-aware names:", numericAware);
|
||||||
|
|
||||||
|
JCheckBox ignoreLeadingDot = new JCheckBox("Ignore leading dot in names (treat '.name' as 'name')");
|
||||||
|
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.get(0).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) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
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("uppercasePriority", uppercasePriority);
|
||||||
|
holder.putClientProperty("numericAware", numericAware);
|
||||||
|
holder.putClientProperty("ignoreLeadingDot", ignoreLeadingDot);
|
||||||
|
panels.put("Sorting", holder);
|
||||||
|
|
||||||
|
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) {
|
private String getFontDescription(Font f) {
|
||||||
if (f == null) return "(default)";
|
if (f == null) return "(default)";
|
||||||
return String.format("%s %dpt", f.getName(), f.getSize());
|
return String.format("%s %dpt", f.getName(), f.getSize());
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user