From 609e96145d99d8d242a71b645fbf56e423dbc355 Mon Sep 17 00:00:00 2001 From: Radek Davidek Date: Fri, 23 Jan 2026 15:00:33 +0100 Subject: [PATCH] added item info ctrl+q --- .../java/cz/kamma/kfmanager/ui/FilePanel.java | 12 ++ .../cz/kamma/kfmanager/ui/FilePanelTab.java | 182 +++++++++++++++--- .../cz/kamma/kfmanager/ui/MainWindow.java | 38 +++- .../java/cz/kamma/kfmanager/ui/ViewMode.java | 3 +- 4 files changed, 206 insertions(+), 29 deletions(-) diff --git a/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java b/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java index d60247e..a584c25 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java +++ b/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java @@ -745,6 +745,18 @@ public class FilePanel extends JPanel { FilePanelTab tab = getCurrentTab(); return tab != null ? tab.getViewMode() : ViewMode.FULL; } + + public ViewMode getPreviousViewMode() { + FilePanelTab tab = getCurrentTab(); + return tab != null ? tab.getPreviousViewMode() : ViewMode.FULL; + } + + public void setInfoItem(FileItem item) { + FilePanelTab tab = getCurrentTab(); + if (tab != null) { + tab.setInfoItem(item); + } + } public void loadDirectory(File directory) { loadDirectory(directory, true); diff --git a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java index c223094..fbc8803 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java +++ b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java @@ -39,6 +39,10 @@ import com.github.junrar.rarfile.FileHeader; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.PosixFileAttributes; +import java.nio.file.attribute.PosixFilePermissions; +import java.nio.file.attribute.DosFileAttributes; /** * Single tab in a panel - displays the contents of one directory @@ -50,6 +54,11 @@ public class FilePanelTab extends JPanel { private FileTableModel tableModel; private JLabel statusLabel; private ViewMode viewMode = ViewMode.FULL; + private ViewMode previousViewMode = ViewMode.FULL; // Store mode before switching to INFO + private JPanel cardPanel; + private CardLayout cardLayout; + private JTextArea infoTextArea; + private JScrollPane infoScrollPane; private int briefCurrentColumn = 0; private Runnable onDirectoryChanged; private Runnable onSwitchPanelRequested; @@ -170,6 +179,7 @@ public class FilePanelTab extends JPanel { public void applyGlobalFont(Font font) { if (font == null) return; fileTable.setFont(font); + infoTextArea.setFont(font); statusLabel.setFont(font); // Update row height based on font metrics FontMetrics fm = fileTable.getFontMetrics(font); @@ -587,6 +597,18 @@ public class FilePanelTab extends JPanel { // Enable horizontal scrollbar when needed so BRIEF mode can scroll left-right scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); + // Info panel for Quick View (Ctrl+Q) + infoTextArea = new JTextArea(); + infoTextArea.setEditable(false); + infoTextArea.setFont(new Font("Monospaced", Font.PLAIN, 12)); + infoTextArea.setMargin(new Insets(10, 10, 10, 10)); + infoScrollPane = new JScrollPane(infoTextArea); + + cardLayout = new CardLayout(); + cardPanel = new JPanel(cardLayout); + cardPanel.add(scrollPane, "TABLE"); + cardPanel.add(infoScrollPane, "INFO"); + // Implement mouse wheel navigation in BRIEF and FULL mode to match arrow key behavior fileTable.addMouseWheelListener(new java.awt.event.MouseWheelListener() { @Override @@ -613,7 +635,7 @@ public class FilePanelTab extends JPanel { } }); - add(scrollPane, BorderLayout.CENTER); + add(cardPanel, BorderLayout.CENTER); // Status bar statusLabel = new JLabel(" "); @@ -2311,13 +2333,16 @@ public class FilePanelTab extends JPanel { public void setViewMode(ViewMode mode, boolean requestFocus) { if (this.viewMode != mode) { + if (this.viewMode != ViewMode.INFO) { + this.previousViewMode = this.viewMode; + } String selectedItemName = null; int selectedRow = fileTable.getSelectedRow(); if (selectedRow >= 0) { FileItem item = null; if (this.viewMode == ViewMode.BRIEF) { item = tableModel.getItemFromBriefLayout(selectedRow, briefCurrentColumn); - } else { + } else if (this.viewMode == ViewMode.FULL) { item = tableModel.getItem(selectedRow); } if (item != null) { @@ -2329,35 +2354,142 @@ public class FilePanelTab extends JPanel { final String itemToSelect = selectedItemName; SwingUtilities.invokeLater(() -> { - 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 (mode == ViewMode.INFO) { + cardLayout.show(cardPanel, "INFO"); } else { - fileTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); - } - // Hide table header in BRIEF mode to save vertical space and match requirements - if (fileTable.getTableHeader() != null) { - fileTable.getTableHeader().setVisible(mode != ViewMode.BRIEF); - } - briefCurrentColumn = 0; - updateColumnRenderers(); - updateColumnWidths(); - fileTable.revalidate(); - fileTable.repaint(); - - if (itemToSelect != null) { - selectItemByName(itemToSelect, requestFocus); - } else if (fileTable.getRowCount() > 0) { - fileTable.setRowSelectionInterval(0, 0); + 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); + } else { + fileTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN); + } + // Hide table header in BRIEF mode to save vertical space and match requirements + if (fileTable.getTableHeader() != null) { + fileTable.getTableHeader().setVisible(mode != ViewMode.BRIEF); + } + briefCurrentColumn = 0; + updateColumnRenderers(); + updateColumnWidths(); + fileTable.revalidate(); + fileTable.repaint(); + + if (itemToSelect != null) { + selectItemByName(itemToSelect, requestFocus); + } else if (fileTable.getRowCount() > 0) { + fileTable.setRowSelectionInterval(0, 0); + } } if (requestFocus) { - fileTable.requestFocusInWindow(); + if (mode == ViewMode.INFO) { + infoTextArea.requestFocusInWindow(); + } else { + fileTable.requestFocusInWindow(); + } } }); } } + + public ViewMode getViewMode() { + return viewMode; + } + + public ViewMode getPreviousViewMode() { + return previousViewMode; + } + + public void setInfoItem(FileItem item) { + if (item == null || item.getName().equals("..")) { + infoTextArea.setText("No item selected."); + return; + } + + File file = item.getFile(); + StringBuilder sb = new StringBuilder(); + sb.append("Name: ").append(file.getName()).append("\n"); + sb.append("Path: ").append(file.getAbsolutePath()).append("\n\n"); + sb.append("Modified: ").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(file.lastModified()))).append("\n"); + + if (!file.isDirectory()) { + sb.append("Type: File\n"); + long size = file.length(); + sb.append("Size: ").append(FileItem.formatSize(size)).append(" (").append(size).append(" bytes)\n"); + addAttributes(sb, file); + infoTextArea.setText(sb.toString()); + infoTextArea.setCaretPosition(0); + } else { + sb.append("Type: Directory\n"); + infoTextArea.setText(sb.toString()); + + // Calculate size and count contents in background to avoid freezing UI + new SwingWorker() { + @Override + protected long[] doInBackground() { + long[] results = new long[3]; // [size, filesCount, dirsCount] + calculateDirStats(file, results); + return results; + } + + @Override + protected void done() { + try { + long[] res = get(); + sb.append("Total Size: ").append(FileItem.formatSize(res[0])).append(" (").append(res[0]).append(" bytes)\n"); + sb.append("Contains: ").append(res[1]).append(" files, ").append(res[2]).append(" directories\n"); + addAttributes(sb, file); + infoTextArea.setText(sb.toString()); + infoTextArea.setCaretPosition(0); + } catch (Exception e) { + sb.append("Error calculating stats: ").append(e.getMessage()); + infoTextArea.setText(sb.toString()); + } + } + }.execute(); + } + } + + private void addAttributes(StringBuilder sb, File file) { + sb.append("\nAttributes:\n"); + try { + Path path = file.toPath(); + BasicFileAttributes basic = Files.readAttributes(path, BasicFileAttributes.class); + sb.append(" Created: ").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(basic.creationTime().toMillis()))).append("\n"); + sb.append(" Last access: ").append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(basic.lastAccessTime().toMillis()))).append("\n"); + + if (Files.getFileStore(path).supportsFileAttributeView("posix")) { + PosixFileAttributes posix = Files.readAttributes(path, PosixFileAttributes.class); + sb.append(" Owner: ").append(posix.owner().getName()).append("\n"); + sb.append(" Group: ").append(posix.group().getName()).append("\n"); + sb.append(" Permissions: ").append(PosixFilePermissions.toString(posix.permissions())).append("\n"); + } else if (Files.getFileStore(path).supportsFileAttributeView("dos")) { + DosFileAttributes dos = Files.readAttributes(path, DosFileAttributes.class); + sb.append(" Hidden: ").append(dos.isHidden()).append("\n"); + sb.append(" ReadOnly: ").append(dos.isReadOnly()).append("\n"); + sb.append(" System: ").append(dos.isSystem()).append("\n"); + sb.append(" Archive: ").append(dos.isArchive()).append("\n"); + } + } catch (IOException e) { + sb.append(" Error reading attributes: ").append(e.getMessage()).append("\n"); + } + } + + private void calculateDirStats(File dir, long[] results) { + File[] items = dir.listFiles(); + if (items != null) { + for (File item : items) { + if (item.isFile()) { + results[0] += item.length(); + results[1]++; + } else { + results[2]++; + calculateDirStats(item, results); + } + } + } + } private void updateColumnRenderers() { int columnCount = tableModel.getColumnCount(); @@ -2960,10 +3092,6 @@ public class FilePanelTab extends JPanel { return currentDirectory; } - public ViewMode getViewMode() { - return viewMode; - } - // FileTableModel private class FileTableModel extends AbstractTableModel { private List items = new ArrayList<>(); diff --git a/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java b/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java index e568e09..338a35c 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java +++ b/src/main/java/cz/kamma/kfmanager/ui/MainWindow.java @@ -235,6 +235,13 @@ public class MainWindow extends JFrame { } }); + // Add selection listeners for Quick View updates + leftPanel.getFileTable().getSelectionModel().addListSelectionListener(e -> { + if (!e.getValueIsAdjusting() && activePanel == leftPanel) { + updateQuickViewInfo(); + } + }); + rightPanel.getFileTable().addFocusListener(new FocusAdapter() { @Override public void focusGained(FocusEvent e) { @@ -255,6 +262,13 @@ public class MainWindow extends JFrame { } }); + // Add selection listeners for Quick View updates + rightPanel.getFileTable().getSelectionModel().addListSelectionListener(e -> { + if (!e.getValueIsAdjusting() && activePanel == rightPanel) { + updateQuickViewInfo(); + } + }); + // Click on panel anywhere should request focus to its table leftPanel.addMouseListener(new MouseAdapter() { @Override @@ -463,7 +477,6 @@ public class MainWindow extends JFrame { // Load custom shortcuts from config List shortcuts = config.getToolbarShortcuts(); - int btnSize = config.getToolbarButtonSize(); int iconSize = config.getToolbarIconSize(); // Group shortcuts: directories will go to the right, others stay on the left @@ -1200,6 +1213,11 @@ public class MainWindow extends JFrame { }, KeyStroke.getKeyStroke(KeyEvent.VK_V, InputEvent.CTRL_DOWN_MASK), JComponent.WHEN_IN_FOCUSED_WINDOW); + // Ctrl+Q - Quick View + rootPane.registerKeyboardAction(e -> toggleQuickView(), + KeyStroke.getKeyStroke(KeyEvent.VK_Q, InputEvent.CTRL_DOWN_MASK), + JComponent.WHEN_IN_FOCUSED_WINDOW); + // Ctrl+E - Command line history rootPane.registerKeyboardAction(e -> showCommandLineHistory(), KeyStroke.getKeyStroke(KeyEvent.VK_E, InputEvent.CTRL_DOWN_MASK), @@ -1222,6 +1240,24 @@ public class MainWindow extends JFrame { /** * Switch between panels */ + private void toggleQuickView() { + FilePanel opposite = (activePanel == leftPanel) ? rightPanel : leftPanel; + if (opposite.getViewMode() == ViewMode.INFO) { + opposite.setViewMode(opposite.getPreviousViewMode(), false); + } else { + opposite.setViewMode(ViewMode.INFO, false); + updateQuickViewInfo(); + } + } + + private void updateQuickViewInfo() { + FilePanel opposite = (activePanel == leftPanel) ? rightPanel : leftPanel; + if (opposite.getViewMode() == ViewMode.INFO) { + FileItem selected = activePanel.getFocusedItem(); + opposite.setInfoItem(selected); + } + } + private void switchPanels() { // Determine which panel currently (or recently) has focus by inspecting the focus owner. java.awt.Component owner = java.awt.KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); diff --git a/src/main/java/cz/kamma/kfmanager/ui/ViewMode.java b/src/main/java/cz/kamma/kfmanager/ui/ViewMode.java index 95799bc..4d57ac9 100644 --- a/src/main/java/cz/kamma/kfmanager/ui/ViewMode.java +++ b/src/main/java/cz/kamma/kfmanager/ui/ViewMode.java @@ -5,5 +5,6 @@ package cz.kamma.kfmanager.ui; */ public enum ViewMode { FULL, // Full details (name, size, date) - BRIEF // Names only in multiple columns + BRIEF, // Names only in multiple columns + INFO // Information about selected file from opposite panel }