From 7529cb31eb352d5c3a05a31bbd1a9fcce9e9704f Mon Sep 17 00:00:00 2001 From: Radek Davidek Date: Tue, 18 Nov 2025 09:32:15 +0100 Subject: [PATCH] test on linux --- .../java/com/kfmanager/ui/FilePanelTab.java | 171 +++++++++++++++++- 1 file changed, 167 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/kfmanager/ui/FilePanelTab.java b/src/main/java/com/kfmanager/ui/FilePanelTab.java index 3b4b878..492cdf8 100644 --- a/src/main/java/com/kfmanager/ui/FilePanelTab.java +++ b/src/main/java/com/kfmanager/ui/FilePanelTab.java @@ -6,6 +6,7 @@ import javax.swing.*; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import java.awt.*; +import java.awt.datatransfer.StringSelection; import java.io.File; import java.io.IOException; import java.util.ArrayList; @@ -166,6 +167,44 @@ public class FilePanelTab extends JPanel { fileTable = new JTable(tableModel) { @Override protected void processMouseEvent(java.awt.event.MouseEvent e) { + // Show system-like context menu on popup trigger (right-click) without + // changing default mouse-driven selection behavior. + try { + if (e.isPopupTrigger()) { + int col = columnAtPoint(e.getPoint()); + int row = rowAtPoint(e.getPoint()); + FileItem item = null; + if (row >= 0) { + if (viewMode == ViewMode.BRIEF) { + item = tableModel.getItemFromBriefLayout(row, col); + if (item != null) { + int index = tableModel.items.indexOf(item); + if (index >= 0) { + 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); + } + } + } else { + item = tableModel.getItem(row); + if (item != null) { + fileTable.setRowSelectionInterval(row, row); + } + } + } + + if (item != null) { + // Delegate to outer class to build and show the menu + FilePanelTab.this.showContextMenuForItem(item, e.getPoint()); + } + e.consume(); + return; + } + } catch (Exception ex) { + // best-effort: ignore exceptions here to avoid breaking mouse handling + } // Intercept mouse events to prevent changing selection via mouse. // Allow single-click to toggle marking of an item, allow BRIEF column // tracking, and preserve double-click to open items. Dragging/presses @@ -808,6 +847,129 @@ public class FilePanelTab extends JPanel { loadDirectory(item.getFile()); } } + + /** + * 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(); + + // Open + JMenuItem openItem = new JMenuItem("Open"); + openItem.addActionListener(ae -> { + if (item.getName().equals("..")) return; + if (item.isDirectory()) { + loadDirectory(item.getFile()); + } else { + try { + if (Desktop.isDesktopSupported()) { + Desktop.getDesktop().open(item.getFile()); + } + } catch (Exception ex) { + try { JOptionPane.showMessageDialog(FilePanelTab.this, "Cannot open: " + ex.getMessage()); } catch (Exception ignore) {} + } + } + }); + menu.add(openItem); + + // Edit + JMenuItem editItem = new JMenuItem("Edit"); + editItem.addActionListener(ae -> { + if (item.isDirectory() || item.getName().equals("..")) return; + try { + String ext = persistedConfig != null ? persistedConfig.getExternalEditorPath() : null; + if (ext != null && !ext.trim().isEmpty()) { + java.util.List cmd = new java.util.ArrayList<>(); + cmd.add(ext); + cmd.add(item.getFile().getAbsolutePath()); + new ProcessBuilder(cmd).start(); + } else if (Desktop.isDesktopSupported()) { + Desktop.getDesktop().edit(item.getFile()); + } + } catch (Exception ex) { + try { JOptionPane.showMessageDialog(FilePanelTab.this, "Cannot edit: " + ex.getMessage()); } catch (Exception ignore) {} + } + }); + menu.add(editItem); + + // Show in folder / Reveal + JMenuItem revealItem = new JMenuItem("Show in folder"); + revealItem.addActionListener(ae -> { + try { + String os = System.getProperty("os.name").toLowerCase(); + File f = item.getFile(); + if (os.contains("win")) { + new ProcessBuilder("explorer.exe", "/select,", f.getAbsolutePath()).start(); + } else if (os.contains("mac")) { + new ProcessBuilder("open", "-R", f.getAbsolutePath()).start(); + } else { + File parent = f.getParentFile(); + if (parent != null) new ProcessBuilder("xdg-open", parent.getAbsolutePath()).start(); + } + } catch (Exception ex) { + try { JOptionPane.showMessageDialog(FilePanelTab.this, "Cannot reveal file: " + ex.getMessage()); } catch (Exception ignore) {} + } + }); + menu.add(revealItem); + + // Copy path + JMenuItem copyPath = new JMenuItem("Copy path"); + copyPath.addActionListener(ae -> { + try { + StringSelection sel = new StringSelection(item.getFile().getAbsolutePath()); + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(sel, null); + } catch (Exception ex) { + try { JOptionPane.showMessageDialog(FilePanelTab.this, "Cannot copy path: " + ex.getMessage()); } catch (Exception ignore) {} + } + }); + menu.add(copyPath); + + // Delete + JMenuItem deleteItem = new JMenuItem("Delete"); + deleteItem.addActionListener(ae -> { + int res = JOptionPane.showConfirmDialog(FilePanelTab.this, + "Really delete '" + item.getName() + "'?", + "Delete", + JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE); + if (res == JOptionPane.YES_OPTION) { + try { + java.util.List toDelete = new java.util.ArrayList<>(); + toDelete.add(item); + com.kfmanager.service.FileOperations.delete(toDelete, null); + // reload current directory + loadDirectory(getCurrentDirectory()); + } catch (Exception ex) { + try { JOptionPane.showMessageDialog(FilePanelTab.this, "Delete failed: " + ex.getMessage()); } catch (Exception ignore) {} + } + } + }); + menu.add(deleteItem); + + // Properties + JMenuItem props = new JMenuItem("Properties"); + props.addActionListener(ae -> { + try { + File f = item.getFile(); + String info = String.format("Name: %s\nPath: %s\nSize: %s\nModified: %s\nReadable: %b, Writable: %b, Executable: %b", + f.getName(), f.getAbsolutePath(), item.isDirectory() ? "" : formatSize(f.length()), + new java.util.Date(f.lastModified()).toString(), f.canRead(), f.canWrite(), f.canExecute()); + JOptionPane.showMessageDialog(FilePanelTab.this, info, "Properties", JOptionPane.INFORMATION_MESSAGE); + } catch (Exception ex) { + try { JOptionPane.showMessageDialog(FilePanelTab.this, "Cannot show properties: " + ex.getMessage()); } catch (Exception ignore) {} + } + }); + menu.add(props); + + // Show the popup at the click location + SwingUtilities.invokeLater(() -> { + try { + menu.show(fileTable, tablePoint.x, tablePoint.y); + } catch (Exception ignore) {} + }); + } public void navigateUp() { // If we're currently browsing an extracted archive root, navigate back to the @@ -1064,11 +1226,11 @@ public class FilePanelTab extends JPanel { } else { 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()) { setBackground(selectionColor); - } else if (isCurrentCell) { - setBackground(selectionInactiveColor); } else { setBackground(FilePanelTab.this.getBackground()); } @@ -1085,7 +1247,8 @@ public class FilePanelTab extends JPanel { } else { // Preserve whatever style the base font has (do not force plain) setFont(baseFont.deriveFont(baseStyle)); - setForeground(Color.BLACK); + // Use a light foreground color for better contrast on dark backgrounds + setForeground(new Color(240, 240, 240)); } // Zobrazit ikonu pro názvy souborů