From a67b92b0155962a70cfd9545f0596072c933a000 Mon Sep 17 00:00:00 2001 From: Radek Davidek Date: Sun, 8 Mar 2026 16:17:04 +0100 Subject: [PATCH] added sorting, some fixes --- .../cz/kamma/kfmanager/ui/FilePanelTab.java | 233 ++++++++++++++++-- 1 file changed, 209 insertions(+), 24 deletions(-) diff --git a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java index ec726c3..9ed32b6 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java +++ b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java @@ -59,6 +59,11 @@ public class FilePanelTab extends JPanel { private int sortColumn = -1; // 0=name,1=size,2=date private boolean sortAscending = true; private cz.kamma.kfmanager.config.AppConfig persistedConfig; + // Sorting buttons for BRIEF mode + private JPanel briefSortPanel; + private JButton sortByNameButton; + private JButton sortByDateButton; + private JButton sortBySizeButton; // Track last selection to restore it if focus is requested on empty area private int lastValidRow = 0; private int lastValidBriefColumn = 0; @@ -810,6 +815,84 @@ public class FilePanelTab extends JPanel { } }); + // Create sorting buttons panel for BRIEF mode with GridLayout for full width + briefSortPanel = new JPanel(new GridLayout(1, 3, 0, 0)); + briefSortPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); + + sortByNameButton = new JButton("Name"); + sortByDateButton = new JButton("Date"); + sortBySizeButton = new JButton("Size"); + + // Set buttons to minimal height + sortByNameButton.setPreferredSize(new Dimension(0, 20)); + sortByDateButton.setPreferredSize(new Dimension(0, 20)); + sortBySizeButton.setPreferredSize(new Dimension(0, 20)); + + briefSortPanel.add(sortByNameButton); + briefSortPanel.add(sortByDateButton); + briefSortPanel.add(sortBySizeButton); + briefSortPanel.setVisible(false); // Hidden by default, shown only in BRIEF mode + briefSortPanel.setPreferredSize(new Dimension(0, 20)); + + // Setup sorting button listeners + sortByNameButton.addActionListener(e -> { + if (sortColumn == 0) { + sortAscending = !sortAscending; + } else { + sortColumn = 0; + sortAscending = true; + } + sortItemsByColumn(sortColumn, sortAscending); + tableModel.fireTableDataChanged(); + updateStatus(); + updateSortButtonsDisplay(); + if (persistedConfig != null) { + persistedConfig.setDefaultSortColumn(sortColumn); + persistedConfig.setDefaultSortAscending(sortAscending); + persistedConfig.saveConfig(); + } + fileTable.requestFocus(); + }); + + sortByDateButton.addActionListener(e -> { + if (sortColumn == 2) { + sortAscending = !sortAscending; + } else { + sortColumn = 2; + sortAscending = false; // Default: newest first + } + sortItemsByColumn(sortColumn, sortAscending); + tableModel.fireTableDataChanged(); + updateStatus(); + updateSortButtonsDisplay(); + if (persistedConfig != null) { + persistedConfig.setDefaultSortColumn(sortColumn); + persistedConfig.setDefaultSortAscending(sortAscending); + persistedConfig.saveConfig(); + } + fileTable.requestFocus(); + }); + + sortBySizeButton.addActionListener(e -> { + if (sortColumn == 1) { + sortAscending = !sortAscending; + } else { + sortColumn = 1; + sortAscending = false; // Default: largest first + } + sortItemsByColumn(sortColumn, sortAscending); + tableModel.fireTableDataChanged(); + updateStatus(); + updateSortButtonsDisplay(); + if (persistedConfig != null) { + persistedConfig.setDefaultSortColumn(sortColumn); + persistedConfig.setDefaultSortAscending(sortAscending); + persistedConfig.saveConfig(); + } + fileTable.requestFocus(); + }); + + add(briefSortPanel, BorderLayout.NORTH); add(cardPanel, BorderLayout.CENTER); // Status bar @@ -1222,7 +1305,20 @@ public class FilePanelTab extends JPanel { } List items = (preloadedItems != null) ? preloadedItems : createFileItemList(directory); - tableModel.setItems(items); + + // Only update the model if items have actually changed + boolean itemsChanged = !isSameContent(items, tableModel.items); + if (itemsChanged) { + tableModel.setItems(items); + } else { + // Items haven't changed, but we might still need to apply sort or revalidate + tableModel.items = items; + } + + // Apply saved sort settings to newly loaded items + if (sortColumn >= 0) { + sortItemsByColumn(sortColumn, sortAscending); + } if (viewMode == ViewMode.BRIEF) { boolean selectFirst = autoSelectFirst; @@ -1346,6 +1442,8 @@ public class FilePanelTab extends JPanel { } final List itemsToLoad = newItems; + final boolean contentChanged = !isSameContent(newItems, tableModel.items); + loadDirectory(currentDirectory, itemsToLoad, false, requestFocus, () -> { // Restore marks and set recentlyChanged flag based on active timestamps for (FileItem item : tableModel.items) { @@ -1409,7 +1507,9 @@ public class FilePanelTab extends JPanel { } } } - fileTable.repaint(); + if (contentChanged) { + fileTable.repaint(); + } }); } @@ -2633,14 +2733,20 @@ public class FilePanelTab extends JPanel { SwingUtilities.invokeLater(() -> { if (mode == ViewMode.INFO) { cardLayout.show(cardPanel, "INFO"); + if (briefSortPanel != null) briefSortPanel.setVisible(false); } else { cardLayout.show(cardPanel, "TABLE"); tableModel.updateViewMode(mode); // Switch auto-resize behavior depending on mode so BRIEF can scroll horizontally if (mode == ViewMode.BRIEF) { fileTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); + if (briefSortPanel != null) { + briefSortPanel.setVisible(true); + updateSortButtonsDisplay(); + } } else { fileTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); + if (briefSortPanel != null) briefSortPanel.setVisible(false); } // Hide table header in BRIEF mode to save vertical space and match requirements if (fileTable.getTableHeader() != null) { @@ -3118,39 +3224,92 @@ public class FilePanelTab extends JPanel { * Sort items in the current table model according to column (FULL mode). * column: 0=name, 1=size, 2=date */ + private void updateSortButtonsDisplay() { + if (sortByNameButton == null || sortByDateButton == null || sortBySizeButton == null) return; + + String upArrow = "↑"; + String downArrow = "↓"; + + sortByNameButton.setText(sortColumn == 0 ? "Name " + (sortAscending ? upArrow : downArrow) : "Name"); + sortByDateButton.setText(sortColumn == 2 ? "Date " + (sortAscending ? upArrow : downArrow) : "Date"); + sortBySizeButton.setText(sortColumn == 1 ? "Size " + (sortAscending ? upArrow : downArrow) : "Size"); + } + + /** + * Remove leading and trailing $ characters for sorting purposes. + */ + private String getCleanNameForSorting(String name) { + if (name == null) return ""; + return name.replaceAll("^\\$+|\\$+$", ""); + } + private void sortItemsByColumn(int column, boolean asc) { if (tableModel == null || tableModel.items == null) return; java.util.List items = tableModel.items; if (items.isEmpty()) return; + // Remember currently selected item name to restore selection after sort + final String selectedItemName; + FileItem focused = getFocusedItem(); + selectedItemName = (focused != null) ? focused.getName() : null; + + // Extract and remember the ".." (parent directory) entry if present + FileItem parentDir = null; + if (!items.isEmpty() && items.get(0).getName().equals("..")) { + 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; switch (column) { case 1: // size comp = (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()); + if (r == 0) r = getCleanNameForSorting(a.getName()).compareToIgnoreCase(getCleanNameForSorting(b.getName())); return r; }; break; case 2: // date - // Sort by modification time regardless of directory flag so "newest first" places the newest item + // Sort by modification time comp = (a, b) -> { int r = Long.compare(a.getModified().getTime(), b.getModified().getTime()); - if (r == 0) r = a.getName().compareToIgnoreCase(b.getName()); + if (r == 0) r = getCleanNameForSorting(a.getName()).compareToIgnoreCase(getCleanNameForSorting(b.getName())); return r; }; break; default: // name - comp = (a, b) -> compareFileItemsByName(a, b); + comp = (a, b) -> { + String s1 = getCleanNameForSorting(a.getName()); + String s2 = getCleanNameForSorting(b.getName()); + return s1.compareToIgnoreCase(s2); + }; break; } if (!asc) comp = comp.reversed(); - items.sort(comp); + // Sort both lists separately + directories.sort(comp); + files.sort(comp); + + // Clear and rebuild items list: directories first, then files + items.clear(); + if (parentDir != null) { + items.add(parentDir); + } + items.addAll(directories); + items.addAll(files); // Refresh table on EDT SwingUtilities.invokeLater(() -> { @@ -3163,6 +3322,26 @@ public class FilePanelTab extends JPanel { updateColumnRenderers(); updateColumnWidths(); if (fileTable.getTableHeader() != null) fileTable.getTableHeader().repaint(); + + // Restore selection to the same item (by name) after sorting + if (selectedItemName != null) { + for (int i = 0; i < tableModel.items.size(); i++) { + if (tableModel.items.get(i).getName().equals(selectedItemName)) { + if (viewMode == ViewMode.BRIEF) { + int selRow = i % tableModel.briefRowsPerColumn; + int selCol = i / tableModel.briefRowsPerColumn; + briefCurrentColumn = selCol; + fileTable.setRowSelectionInterval(selRow, selRow); + fileTable.getColumnModel().getSelectionModel().setSelectionInterval(selCol, selCol); + fileTable.scrollRectToVisible(fileTable.getCellRect(selRow, selCol, true)); + } else { + fileTable.setRowSelectionInterval(i, i); + fileTable.scrollRectToVisible(fileTable.getCellRect(i, 0, true)); + } + break; + } + } + } }); } @@ -3351,25 +3530,31 @@ public class FilePanelTab extends JPanel { public void setAppConfig(cz.kamma.kfmanager.config.AppConfig cfg) { this.persistedConfig = cfg; iconCache.clear(); - // Apply persisted sort if present + + // Apply persisted sort if present - try global.sort.column first, then MultipleSortCriteria if (cfg != null) { - java.util.List multi = cfg.getMultipleSortCriteria(); - if (multi != null && !multi.isEmpty()) { - applyMultipleSortCriteria(multi); - } else { - int col = cfg.getDefaultSortColumn(); - boolean asc = cfg.getDefaultSortAscending(); - if (col >= 0) { - this.sortColumn = col; - this.sortAscending = asc; + 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); - SwingUtilities.invokeLater(() -> { - tableModel.fireTableDataChanged(); - if (fileTable.getTableHeader() != null) fileTable.getTableHeader().repaint(); - }); + } + } else { + // No global sort setting, try multiple criteria + java.util.List multi = cfg.getMultipleSortCriteria(); + if (multi != null && !multi.isEmpty()) { + applyMultipleSortCriteria(multi); } } } + + // Update button display immediately (not in invokeLater) so ViewMode changes can see updated state + updateSortButtonsDisplay(); } /**