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