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 cz.kamma.kfmanager.config.AppConfig appConfig;
|
||||||
private Runnable onDirectoryChangedAll;
|
private Runnable onDirectoryChangedAll;
|
||||||
private boolean ignoreComboActions = false;
|
private boolean ignoreComboActions = false;
|
||||||
|
private boolean active = false;
|
||||||
|
|
||||||
public FilePanel(String initialPath) {
|
public FilePanel(String initialPath) {
|
||||||
initComponents();
|
initComponents();
|
||||||
@ -263,6 +264,7 @@ public class FilePanel extends JPanel {
|
|||||||
|
|
||||||
FilePanelTab tab = new FilePanelTab(path, requestFocus);
|
FilePanelTab tab = new FilePanelTab(path, requestFocus);
|
||||||
if (appConfig != null) tab.setAppConfig(appConfig);
|
if (appConfig != null) tab.setAppConfig(appConfig);
|
||||||
|
tab.setActive(this.active);
|
||||||
|
|
||||||
// Set callback for updating tab title on directory change
|
// Set callback for updating tab title on directory change
|
||||||
tab.setOnDirectoryChanged(() -> {
|
tab.setOnDirectoryChanged(() -> {
|
||||||
@ -309,6 +311,7 @@ public class FilePanel extends JPanel {
|
|||||||
public void addNewTabWithMode(String path, ViewMode mode, boolean requestFocus) {
|
public void addNewTabWithMode(String path, ViewMode mode, boolean requestFocus) {
|
||||||
FilePanelTab tab = new FilePanelTab(path, requestFocus);
|
FilePanelTab tab = new FilePanelTab(path, requestFocus);
|
||||||
if (appConfig != null) tab.setAppConfig(appConfig);
|
if (appConfig != null) tab.setAppConfig(appConfig);
|
||||||
|
tab.setActive(this.active);
|
||||||
tab.setOnDirectoryChanged(() -> {
|
tab.setOnDirectoryChanged(() -> {
|
||||||
updateTabTitle(tab);
|
updateTabTitle(tab);
|
||||||
if (onDirectoryChangedAll != null) onDirectoryChangedAll.run();
|
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.
|
* The indicator is placed between the drive dropdown and the tabs.
|
||||||
*/
|
*/
|
||||||
public void setActive(boolean active, Color activeColor) {
|
public void setActive(boolean active, Color activeColor) {
|
||||||
|
this.active = active;
|
||||||
// Inactive indicator is subtle dark gray
|
// Inactive indicator is subtle dark gray
|
||||||
Color inactiveColor = new Color(60, 60, 60);
|
Color inactiveColor = new Color(60, 60, 60);
|
||||||
Color targetColor = active ? activeColor : inactiveColor;
|
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
|
// Apply a matte border to the top of tabbedPane - this places it between
|
||||||
// the topPanel (dropdown) and the tabs themselves.
|
// the topPanel (dropdown) and the tabs themselves.
|
||||||
tabbedPane.setBorder(BorderFactory.createMatteBorder(3, 0, 0, 0, targetColor));
|
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();
|
revalidate();
|
||||||
repaint();
|
repaint();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -68,6 +68,7 @@ public class FilePanelTab extends JPanel {
|
|||||||
private Path currentArchiveTempDir = null;
|
private Path currentArchiveTempDir = null;
|
||||||
private File currentArchiveSourceFile = null;
|
private File currentArchiveSourceFile = null;
|
||||||
private boolean inlineRenameActive = false;
|
private boolean inlineRenameActive = false;
|
||||||
|
private boolean active = false;
|
||||||
|
|
||||||
public FilePanelTab(String initialPath) {
|
public FilePanelTab(String initialPath) {
|
||||||
this(initialPath, true);
|
this(initialPath, true);
|
||||||
@ -241,6 +242,13 @@ public class FilePanelTab extends JPanel {
|
|||||||
public void setOnSwitchPanelRequested(Runnable callback) {
|
public void setOnSwitchPanelRequested(Runnable callback) {
|
||||||
this.onSwitchPanelRequested = callback;
|
this.onSwitchPanelRequested = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setActive(boolean active) {
|
||||||
|
this.active = active;
|
||||||
|
if (fileTable != null) {
|
||||||
|
fileTable.repaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void initComponents() {
|
private void initComponents() {
|
||||||
setLayout(new BorderLayout());
|
setLayout(new BorderLayout());
|
||||||
@ -270,6 +278,7 @@ public class FilePanelTab extends JPanel {
|
|||||||
// changing default mouse-driven selection behavior.
|
// changing default mouse-driven selection behavior.
|
||||||
try {
|
try {
|
||||||
if (e.isPopupTrigger()) {
|
if (e.isPopupTrigger()) {
|
||||||
|
fileTable.requestFocusInWindow();
|
||||||
int col = columnAtPoint(e.getPoint());
|
int col = columnAtPoint(e.getPoint());
|
||||||
int row = rowAtPoint(e.getPoint());
|
int row = rowAtPoint(e.getPoint());
|
||||||
FileItem item = null;
|
FileItem item = null;
|
||||||
@ -282,14 +291,14 @@ public class FilePanelTab extends JPanel {
|
|||||||
int selRow = index % tableModel.briefRowsPerColumn;
|
int selRow = index % tableModel.briefRowsPerColumn;
|
||||||
int selCol = index / tableModel.briefRowsPerColumn;
|
int selCol = index / tableModel.briefRowsPerColumn;
|
||||||
briefCurrentColumn = selCol;
|
briefCurrentColumn = selCol;
|
||||||
// Select the logical row so actions apply to this item
|
// Focus and Select the item
|
||||||
fileTable.setRowSelectionInterval(selRow, selRow);
|
fileTable.changeSelection(selRow, selCol, false, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
item = tableModel.getItem(row);
|
item = tableModel.getItem(row);
|
||||||
if (item != null) {
|
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()) {
|
if (fileTable.isEditing()) {
|
||||||
fileTable.getCellEditor().cancelCellEditing();
|
fileTable.getCellEditor().cancelCellEditing();
|
||||||
e.consume();
|
e.consume();
|
||||||
|
} else {
|
||||||
|
// Close any active context menu or other popups
|
||||||
|
MenuSelectionManager.defaultManager().clearSelectedPath();
|
||||||
}
|
}
|
||||||
} else if (viewMode == ViewMode.BRIEF) {
|
} else if (viewMode == ViewMode.BRIEF) {
|
||||||
handleBriefKeyNavigation(e);
|
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).
|
* Show a context popup menu for a specific file item at the given point (table coordinates).
|
||||||
*/
|
*/
|
||||||
private void showContextMenuForItem(FileItem item, Point tablePoint) {
|
private void showContextMenuForItem(FileItem item, Point tablePoint) {
|
||||||
if (item == null) return;
|
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
|
// Open
|
||||||
JMenuItem openItem = new JMenuItem("Open");
|
JMenuItem openItem = new JMenuItem("Open");
|
||||||
|
setMenuItemEnabled(openItem, !isParentDir);
|
||||||
openItem.addActionListener(ae -> {
|
openItem.addActionListener(ae -> {
|
||||||
if (item.getName().equals("..")) return;
|
if (isParentDir) return;
|
||||||
if (item.isDirectory()) {
|
if (isDir) {
|
||||||
loadDirectory(item.getFile());
|
loadDirectory(item.getFile());
|
||||||
} else {
|
} else {
|
||||||
openFileNative(item.getFile());
|
openFileNative(item.getFile());
|
||||||
@ -1483,10 +1550,10 @@ public class FilePanelTab extends JPanel {
|
|||||||
menu.add(openItem);
|
menu.add(openItem);
|
||||||
|
|
||||||
// Open with
|
// Open with
|
||||||
if (!item.getName().equals("..")) {
|
if (!isParentDir) {
|
||||||
java.util.List<AppConfig.OpenWithEntry> owEntries = persistedConfig != null ? persistedConfig.getOpenWithEntries() : new java.util.ArrayList<>();
|
java.util.List<AppConfig.OpenWithEntry> owEntries = persistedConfig != null ? persistedConfig.getOpenWithEntries() : new java.util.ArrayList<>();
|
||||||
String itemType;
|
String itemType;
|
||||||
if (item.isDirectory()) {
|
if (isDir) {
|
||||||
itemType = "directory";
|
itemType = "directory";
|
||||||
} else {
|
} else {
|
||||||
String name = item.getFile().getName().toLowerCase();
|
String name = item.getFile().getName().toLowerCase();
|
||||||
@ -1509,13 +1576,22 @@ public class FilePanelTab extends JPanel {
|
|||||||
openWithMenu.add(owItem);
|
openWithMenu.add(owItem);
|
||||||
}
|
}
|
||||||
menu.add(openWithMenu);
|
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
|
// Edit
|
||||||
JMenuItem editItem = new JMenuItem("Edit");
|
JMenuItem editItem = new JMenuItem("Edit");
|
||||||
|
setMenuItemEnabled(editItem, !isParentDir && !isDir);
|
||||||
editItem.addActionListener(ae -> {
|
editItem.addActionListener(ae -> {
|
||||||
if (item.isDirectory() || item.getName().equals("..")) return;
|
if (isDir || isParentDir) return;
|
||||||
try {
|
try {
|
||||||
String ext = persistedConfig != null ? persistedConfig.getExternalEditorPath() : null;
|
String ext = persistedConfig != null ? persistedConfig.getExternalEditorPath() : null;
|
||||||
if (ext != null && !ext.trim().isEmpty()) {
|
if (ext != null && !ext.trim().isEmpty()) {
|
||||||
@ -1534,6 +1610,7 @@ public class FilePanelTab extends JPanel {
|
|||||||
|
|
||||||
// Show in folder / Reveal
|
// Show in folder / Reveal
|
||||||
JMenuItem revealItem = new JMenuItem("Show in folder");
|
JMenuItem revealItem = new JMenuItem("Show in folder");
|
||||||
|
setMenuItemEnabled(revealItem, !isParentDir);
|
||||||
revealItem.addActionListener(ae -> {
|
revealItem.addActionListener(ae -> {
|
||||||
try {
|
try {
|
||||||
String os = System.getProperty("os.name").toLowerCase();
|
String os = System.getProperty("os.name").toLowerCase();
|
||||||
@ -1554,6 +1631,7 @@ public class FilePanelTab extends JPanel {
|
|||||||
|
|
||||||
// Copy path
|
// Copy path
|
||||||
JMenuItem copyPath = new JMenuItem("Copy path");
|
JMenuItem copyPath = new JMenuItem("Copy path");
|
||||||
|
setMenuItemEnabled(copyPath, !isParentDir);
|
||||||
copyPath.addActionListener(ae -> {
|
copyPath.addActionListener(ae -> {
|
||||||
try {
|
try {
|
||||||
StringSelection sel = new StringSelection(item.getFile().getAbsolutePath());
|
StringSelection sel = new StringSelection(item.getFile().getAbsolutePath());
|
||||||
@ -1569,18 +1647,21 @@ public class FilePanelTab extends JPanel {
|
|||||||
// Cut
|
// Cut
|
||||||
JMenuItem cutItem = new JMenuItem("Cut");
|
JMenuItem cutItem = new JMenuItem("Cut");
|
||||||
cutItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK));
|
cutItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, ActionEvent.CTRL_MASK));
|
||||||
|
setMenuItemEnabled(cutItem, !isParentDir);
|
||||||
cutItem.addActionListener(ae -> copyToClipboard(true));
|
cutItem.addActionListener(ae -> copyToClipboard(true));
|
||||||
menu.add(cutItem);
|
menu.add(cutItem);
|
||||||
|
|
||||||
// Copy
|
// Copy
|
||||||
JMenuItem copyItem = new JMenuItem("Copy");
|
JMenuItem copyItem = new JMenuItem("Copy");
|
||||||
copyItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK));
|
copyItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, ActionEvent.CTRL_MASK));
|
||||||
|
setMenuItemEnabled(copyItem, !isParentDir);
|
||||||
copyItem.addActionListener(ae -> copyToClipboard(false));
|
copyItem.addActionListener(ae -> copyToClipboard(false));
|
||||||
menu.add(copyItem);
|
menu.add(copyItem);
|
||||||
|
|
||||||
// Paste
|
// Paste
|
||||||
JMenuItem pasteItem = new JMenuItem("Paste");
|
JMenuItem pasteItem = new JMenuItem("Paste");
|
||||||
pasteItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, ActionEvent.CTRL_MASK));
|
pasteItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, ActionEvent.CTRL_MASK));
|
||||||
|
setMenuItemEnabled(pasteItem, hasClipboardFiles);
|
||||||
pasteItem.addActionListener(ae -> pasteFromClipboard());
|
pasteItem.addActionListener(ae -> pasteFromClipboard());
|
||||||
menu.add(pasteItem);
|
menu.add(pasteItem);
|
||||||
|
|
||||||
@ -1588,6 +1669,7 @@ public class FilePanelTab extends JPanel {
|
|||||||
|
|
||||||
// Delete
|
// Delete
|
||||||
JMenuItem deleteItem = new JMenuItem("Delete");
|
JMenuItem deleteItem = new JMenuItem("Delete");
|
||||||
|
setMenuItemEnabled(deleteItem, !isParentDir);
|
||||||
deleteItem.addActionListener(ae -> {
|
deleteItem.addActionListener(ae -> {
|
||||||
int res = JOptionPane.showConfirmDialog(FilePanelTab.this,
|
int res = JOptionPane.showConfirmDialog(FilePanelTab.this,
|
||||||
"Really delete '" + item.getName() + "'?",
|
"Really delete '" + item.getName() + "'?",
|
||||||
@ -1680,6 +1762,7 @@ public class FilePanelTab extends JPanel {
|
|||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
JMenuItem props = new JMenuItem("Properties");
|
JMenuItem props = new JMenuItem("Properties");
|
||||||
|
setMenuItemEnabled(props, !isParentDir);
|
||||||
props.addActionListener(ae -> {
|
props.addActionListener(ae -> {
|
||||||
try {
|
try {
|
||||||
Window parent = SwingUtilities.getWindowAncestor(FilePanelTab.this);
|
Window parent = SwingUtilities.getWindowAncestor(FilePanelTab.this);
|
||||||
@ -1696,6 +1779,16 @@ public class FilePanelTab extends JPanel {
|
|||||||
});
|
});
|
||||||
menu.add(props);
|
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
|
// Show the popup at the click location
|
||||||
SwingUtilities.invokeLater(() -> {
|
SwingUtilities.invokeLater(() -> {
|
||||||
try {
|
try {
|
||||||
@ -2296,9 +2389,9 @@ public class FilePanelTab extends JPanel {
|
|||||||
isCurrentCell = isSelected;
|
isCurrentCell = isSelected;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only show selection highlight when this table has keyboard focus.
|
// Show selection highlight even when the table doesn't have focus, but only
|
||||||
// If the table is not focused, do not highlight any row (show normal background).
|
// if this panel/tab is the active one.
|
||||||
if (isCurrentCell && table.hasFocus()) {
|
if (isCurrentCell && FilePanelTab.this.active) {
|
||||||
setBackground(selectionColor);
|
setBackground(selectionColor);
|
||||||
} else {
|
} else {
|
||||||
setBackground(FilePanelTab.this.getBackground());
|
setBackground(FilePanelTab.this.getBackground());
|
||||||
@ -2322,7 +2415,7 @@ public class FilePanelTab extends JPanel {
|
|||||||
// Preserve whatever style the base font has (do not force plain)
|
// Preserve whatever style the base font has (do not force plain)
|
||||||
setFont(baseFont.deriveFont(baseStyle));
|
setFont(baseFont.deriveFont(baseStyle));
|
||||||
// Automatically adjust foreground contrast
|
// Automatically adjust foreground contrast
|
||||||
if (isCurrentCell && table.hasFocus()) {
|
if (isCurrentCell && FilePanelTab.this.active) {
|
||||||
setForeground(isDark(selectionColor) ? Color.WHITE : Color.BLACK);
|
setForeground(isDark(selectionColor) ? Color.WHITE : Color.BLACK);
|
||||||
} else {
|
} else {
|
||||||
setForeground(isDark(FilePanelTab.this.getBackground()) ? Color.WHITE : Color.BLACK);
|
setForeground(isDark(FilePanelTab.this.getBackground()) ? Color.WHITE : Color.BLACK);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user