diff --git a/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java b/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java
index 6d0d875..d60247e 100644
--- a/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java
+++ b/src/main/java/cz/kamma/kfmanager/ui/FilePanel.java
@@ -20,6 +20,7 @@ public class FilePanel extends JPanel {
private cz.kamma.kfmanager.config.AppConfig appConfig;
private Runnable onDirectoryChangedAll;
private boolean ignoreComboActions = false;
+ private boolean active = false;
public FilePanel(String initialPath) {
initComponents();
@@ -263,6 +264,7 @@ public class FilePanel extends JPanel {
FilePanelTab tab = new FilePanelTab(path, requestFocus);
if (appConfig != null) tab.setAppConfig(appConfig);
+ tab.setActive(this.active);
// Set callback for updating tab title on directory change
tab.setOnDirectoryChanged(() -> {
@@ -309,6 +311,7 @@ public class FilePanel extends JPanel {
public void addNewTabWithMode(String path, ViewMode mode, boolean requestFocus) {
FilePanelTab tab = new FilePanelTab(path, requestFocus);
if (appConfig != null) tab.setAppConfig(appConfig);
+ tab.setActive(this.active);
tab.setOnDirectoryChanged(() -> {
updateTabTitle(tab);
if (onDirectoryChangedAll != null) onDirectoryChangedAll.run();
@@ -531,6 +534,7 @@ public class FilePanel extends JPanel {
* The indicator is placed between the drive dropdown and the tabs.
*/
public void setActive(boolean active, Color activeColor) {
+ this.active = active;
// Inactive indicator is subtle dark gray
Color inactiveColor = new Color(60, 60, 60);
Color targetColor = active ? activeColor : inactiveColor;
@@ -538,6 +542,15 @@ public class FilePanel extends JPanel {
// Apply a matte border to the top of tabbedPane - this places it between
// the topPanel (dropdown) and the tabs themselves.
tabbedPane.setBorder(BorderFactory.createMatteBorder(3, 0, 0, 0, targetColor));
+
+ // Propagate active state to all tabs
+ for (int i = 0; i < tabbedPane.getTabCount(); i++) {
+ Component c = tabbedPane.getComponentAt(i);
+ if (c instanceof FilePanelTab) {
+ ((FilePanelTab) c).setActive(active);
+ }
+ }
+
revalidate();
repaint();
}
diff --git a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java
index 609e17c..b7fd5e4 100644
--- a/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java
+++ b/src/main/java/cz/kamma/kfmanager/ui/FilePanelTab.java
@@ -68,6 +68,7 @@ public class FilePanelTab extends JPanel {
private Path currentArchiveTempDir = null;
private File currentArchiveSourceFile = null;
private boolean inlineRenameActive = false;
+ private boolean active = false;
public FilePanelTab(String initialPath) {
this(initialPath, true);
@@ -241,6 +242,13 @@ public class FilePanelTab extends JPanel {
public void setOnSwitchPanelRequested(Runnable callback) {
this.onSwitchPanelRequested = callback;
}
+
+ public void setActive(boolean active) {
+ this.active = active;
+ if (fileTable != null) {
+ fileTable.repaint();
+ }
+ }
private void initComponents() {
setLayout(new BorderLayout());
@@ -270,6 +278,7 @@ public class FilePanelTab extends JPanel {
// changing default mouse-driven selection behavior.
try {
if (e.isPopupTrigger()) {
+ fileTable.requestFocusInWindow();
int col = columnAtPoint(e.getPoint());
int row = rowAtPoint(e.getPoint());
FileItem item = null;
@@ -282,14 +291,14 @@ public class FilePanelTab extends JPanel {
int selRow = index % tableModel.briefRowsPerColumn;
int selCol = index / tableModel.briefRowsPerColumn;
briefCurrentColumn = selCol;
- // Select the logical row so actions apply to this item
- fileTable.setRowSelectionInterval(selRow, selRow);
+ // Focus and Select the item
+ fileTable.changeSelection(selRow, selCol, false, false);
}
}
} else {
item = tableModel.getItem(row);
if (item != null) {
- fileTable.setRowSelectionInterval(row, row);
+ fileTable.changeSelection(row, col, false, false);
}
}
}
@@ -678,6 +687,9 @@ public class FilePanelTab extends JPanel {
if (fileTable.isEditing()) {
fileTable.getCellEditor().cancelCellEditing();
e.consume();
+ } else {
+ // Close any active context menu or other popups
+ MenuSelectionManager.defaultManager().clearSelectedPath();
}
} else if (viewMode == ViewMode.BRIEF) {
handleBriefKeyNavigation(e);
@@ -1462,19 +1474,74 @@ public class FilePanelTab extends JPanel {
}
}
+ /**
+ * Helper to set menu item state and ensure it's visually greyed out if disabled.
+ */
+ private void setMenuItemEnabled(JMenuItem item, boolean enabled) {
+ item.setEnabled(enabled);
+ if (!enabled) {
+ String text = item.getText();
+ if (text != null && !text.startsWith("")) {
+ item.setText("" + text + "");
+ }
+ // Ensure HTML rendering even when disabled
+ item.putClientProperty("html.disable", Boolean.FALSE);
+ item.setForeground(Color.GRAY);
+ } else {
+ String text = item.getText();
+ if (text != null && text.startsWith("")) {
+ String plain = text.replaceAll("<[^>]*>", "");
+ item.setText(plain);
+ }
+ item.setForeground(null);
+ }
+ }
+
/**
* Show a context popup menu for a specific file item at the given point (table coordinates).
*/
private void showContextMenuForItem(FileItem item, Point tablePoint) {
if (item == null) return;
- JPopupMenu menu = new JPopupMenu();
+ // Force ALL possible disabled text properties to grey to override System L&F
+ Color forceColor = Color.GRAY;
+ UIManager.put("MenuItem.disabledForeground", forceColor);
+ UIManager.put("Label.disabledForeground", forceColor);
+ UIManager.put("textInactiveText", forceColor);
+ UIManager.put("Button.disabledText", forceColor);
+ UIManager.put("CheckBoxMenuItem.disabledForeground", forceColor);
+ UIManager.put("Menu.disabledForeground", forceColor);
+
+ boolean isParentDir = item.getName().equals("..");
+ boolean isDir = item.isDirectory();
+ boolean hasClipboardFiles = !cz.kamma.kfmanager.service.ClipboardService.getFilesFromClipboard().isEmpty();
+
+ JPopupMenu menu = new JPopupMenu() {
+ @Override
+ public void setVisible(boolean b) {
+ super.setVisible(b);
+ if (b) {
+ // When the menu becomes visible, ensure it can receive key events
+ requestFocusInWindow();
+ }
+ }
+ };
+
+ // Explicitly handle ESCAPE to close the menu
+ menu.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel");
+ menu.getActionMap().put("cancel", new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ menu.setVisible(false);
+ }
+ });
// Open
JMenuItem openItem = new JMenuItem("Open");
+ setMenuItemEnabled(openItem, !isParentDir);
openItem.addActionListener(ae -> {
- if (item.getName().equals("..")) return;
- if (item.isDirectory()) {
+ if (isParentDir) return;
+ if (isDir) {
loadDirectory(item.getFile());
} else {
openFileNative(item.getFile());
@@ -1483,10 +1550,10 @@ public class FilePanelTab extends JPanel {
menu.add(openItem);
// Open with
- if (!item.getName().equals("..")) {
+ if (!isParentDir) {
java.util.List owEntries = persistedConfig != null ? persistedConfig.getOpenWithEntries() : new java.util.ArrayList<>();
String itemType;
- if (item.isDirectory()) {
+ if (isDir) {
itemType = "directory";
} else {
String name = item.getFile().getName().toLowerCase();
@@ -1509,13 +1576,22 @@ public class FilePanelTab extends JPanel {
openWithMenu.add(owItem);
}
menu.add(openWithMenu);
+ } else {
+ JMenuItem openWithMenuItem = new JMenuItem("Open with");
+ setMenuItemEnabled(openWithMenuItem, false);
+ menu.add(openWithMenuItem);
}
+ } else {
+ JMenuItem openWithMenuItem = new JMenuItem("Open with");
+ setMenuItemEnabled(openWithMenuItem, false);
+ menu.add(openWithMenuItem);
}
// Edit
JMenuItem editItem = new JMenuItem("Edit");
+ setMenuItemEnabled(editItem, !isParentDir && !isDir);
editItem.addActionListener(ae -> {
- if (item.isDirectory() || item.getName().equals("..")) return;
+ if (isDir || isParentDir) return;
try {
String ext = persistedConfig != null ? persistedConfig.getExternalEditorPath() : null;
if (ext != null && !ext.trim().isEmpty()) {
@@ -1534,6 +1610,7 @@ public class FilePanelTab extends JPanel {
// Show in folder / Reveal
JMenuItem revealItem = new JMenuItem("Show in folder");
+ setMenuItemEnabled(revealItem, !isParentDir);
revealItem.addActionListener(ae -> {
try {
String os = System.getProperty("os.name").toLowerCase();
@@ -1554,6 +1631,7 @@ public class FilePanelTab extends JPanel {
// Copy path
JMenuItem copyPath = new JMenuItem("Copy path");
+ setMenuItemEnabled(copyPath, !isParentDir);
copyPath.addActionListener(ae -> {
try {
StringSelection sel = new StringSelection(item.getFile().getAbsolutePath());
@@ -1569,18 +1647,21 @@ public class FilePanelTab extends JPanel {
// Cut
JMenuItem cutItem = new JMenuItem("Cut");
cutItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK));
+ setMenuItemEnabled(cutItem, !isParentDir);
cutItem.addActionListener(ae -> copyToClipboard(true));
menu.add(cutItem);
// Copy
JMenuItem copyItem = new JMenuItem("Copy");
copyItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK));
+ setMenuItemEnabled(copyItem, !isParentDir);
copyItem.addActionListener(ae -> copyToClipboard(false));
menu.add(copyItem);
// Paste
JMenuItem pasteItem = new JMenuItem("Paste");
pasteItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, ActionEvent.CTRL_MASK));
+ setMenuItemEnabled(pasteItem, hasClipboardFiles);
pasteItem.addActionListener(ae -> pasteFromClipboard());
menu.add(pasteItem);
@@ -1588,6 +1669,7 @@ public class FilePanelTab extends JPanel {
// Delete
JMenuItem deleteItem = new JMenuItem("Delete");
+ setMenuItemEnabled(deleteItem, !isParentDir);
deleteItem.addActionListener(ae -> {
int res = JOptionPane.showConfirmDialog(FilePanelTab.this,
"Really delete '" + item.getName() + "'?",
@@ -1680,6 +1762,7 @@ public class FilePanelTab extends JPanel {
// Properties
JMenuItem props = new JMenuItem("Properties");
+ setMenuItemEnabled(props, !isParentDir);
props.addActionListener(ae -> {
try {
Window parent = SwingUtilities.getWindowAncestor(FilePanelTab.this);
@@ -1696,6 +1779,16 @@ public class FilePanelTab extends JPanel {
});
menu.add(props);
+ // Ensure focus returns to table when menu is cancelled (e.g. via ESC)
+ menu.addPopupMenuListener(new javax.swing.event.PopupMenuListener() {
+ @Override public void popupMenuWillBecomeVisible(javax.swing.event.PopupMenuEvent e) {}
+ @Override public void popupMenuWillBecomeInvisible(javax.swing.event.PopupMenuEvent e) {
+ // Use invokeLater to ensure the menu is fully closed before requesting focus
+ SwingUtilities.invokeLater(() -> fileTable.requestFocusInWindow());
+ }
+ @Override public void popupMenuCanceled(javax.swing.event.PopupMenuEvent e) {}
+ });
+
// Show the popup at the click location
SwingUtilities.invokeLater(() -> {
try {
@@ -2296,9 +2389,9 @@ public class FilePanelTab extends JPanel {
isCurrentCell = isSelected;
}
- // Only show selection highlight when this table has keyboard focus.
- // If the table is not focused, do not highlight any row (show normal background).
- if (isCurrentCell && table.hasFocus()) {
+ // Show selection highlight even when the table doesn't have focus, but only
+ // if this panel/tab is the active one.
+ if (isCurrentCell && FilePanelTab.this.active) {
setBackground(selectionColor);
} else {
setBackground(FilePanelTab.this.getBackground());
@@ -2322,7 +2415,7 @@ public class FilePanelTab extends JPanel {
// Preserve whatever style the base font has (do not force plain)
setFont(baseFont.deriveFont(baseStyle));
// Automatically adjust foreground contrast
- if (isCurrentCell && table.hasFocus()) {
+ if (isCurrentCell && FilePanelTab.this.active) {
setForeground(isDark(selectionColor) ? Color.WHITE : Color.BLACK);
} else {
setForeground(isDark(FilePanelTab.this.getBackground()) ? Color.WHITE : Color.BLACK);