diff --git a/src/main/java/cz/kamma/kfmanager/config/AppConfig.java b/src/main/java/cz/kamma/kfmanager/config/AppConfig.java index e410fab..b22695a 100644 --- a/src/main/java/cz/kamma/kfmanager/config/AppConfig.java +++ b/src/main/java/cz/kamma/kfmanager/config/AppConfig.java @@ -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")); } diff --git a/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java b/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java index b709267..5e259ad 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java +++ b/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java @@ -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 diff --git a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java index b98418d..fb33740 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java +++ b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java @@ -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 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 list1, List list2) { + if (list1.size() != list2.size()) return false; + + Map 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 directories = new ArrayList<>(); - java.util.List files = new ArrayList<>(); - - for (FileItem item : items) { - if (item.isDirectory()) { - directories.add(item); - } else { - files.add(item); - } - } + java.util.Comparator comp = (a, b) -> compareByColumnWithConfig(a, b, column, asc); + java.util.List sorted = new ArrayList<>(items); + sorted.sort(comp); - java.util.Comparator 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 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 criteria) { - if (criteria == null || criteria.isEmpty()) return; - if (tableModel == null || tableModel.items == null) return; - - java.util.Comparator 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 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 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; diff --git a/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java b/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java index 804ecc0..73d6cef 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java +++ b/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java @@ -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(); diff --git a/src/main/java/cz/kamma/kfmanager/ui/SettingsDialog.java b/src/main/java/cz/kamma/kfmanager/ui/SettingsDialog.java index 993e6f4..5a956d2 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/SettingsDialog.java +++ b/src/main/java/cz/kamma/kfmanager/ui/SettingsDialog.java @@ -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 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 primaryField = new JComboBox<>(new String[]{"Name", "Size", "Date"}); - JComboBox 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 secondaryField = new JComboBox<>(new String[]{"(none)", "Name", "Size", "Date"}); - JComboBox 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 tertiaryField = new JComboBox<>(new String[]{"(none)", "Name", "Size", "Date"}); - JComboBox 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 sortField = new JComboBox<>(new String[]{"Name", "Size", "Date"}); + JComboBox 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 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 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());