added item info ctrl+q

This commit is contained in:
Radek Davidek 2026-01-23 15:00:33 +01:00
parent 7962d8defc
commit 609e96145d
4 changed files with 206 additions and 29 deletions

View File

@ -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);

View File

@ -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<long[], Void>() {
@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<FileItem> items = new ArrayList<>();

View File

@ -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<AppConfig.ToolbarShortcut> 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();

View File

@ -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
}