UI fixes - context menu
This commit is contained in:
parent
c790303438
commit
42ea4fec7a
@ -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();
|
||||
}
|
||||
|
||||
@ -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("<html>")) {
|
||||
item.setText("<html><font color='#808080'>" + text + "</font></html>");
|
||||
}
|
||||
// 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("<html>")) {
|
||||
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<AppConfig.OpenWithEntry> 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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user