associate with added

This commit is contained in:
Radek Davidek 2026-01-16 18:21:16 +01:00
parent f88a6c10a1
commit 0d051f240d
5 changed files with 169 additions and 77 deletions

View File

@ -9,7 +9,7 @@ import javax.swing.*;
*/ */
public class MainApp { public class MainApp {
public static final String APP_VERSION = "0.0.3"; public static final String APP_VERSION = "0.0.4";
public static void main(String[] args) { public static void main(String[] args) {
// Set look and feel to system default // Set look and feel to system default

View File

@ -275,6 +275,13 @@ public class AppConfig {
} }
} }
/** Add or update a single file association */
public void setFileAssociation(String pattern, String command) {
if (pattern == null || pattern.trim().isEmpty()) return;
properties.setProperty("assoc." + pattern.trim(), command);
saveConfig();
}
// --- Appearance (global) settings --- // --- Appearance (global) settings ---
public String getGlobalFontName() { public String getGlobalFontName() {
return properties.getProperty("global.font.name", "Monospaced"); return properties.getProperty("global.font.name", "Monospaced");

View File

@ -17,12 +17,17 @@ public class FilePanel extends JPanel {
private JComboBox<File> driveCombo; private JComboBox<File> driveCombo;
private JLabel driveInfoLabel; private JLabel driveInfoLabel;
private com.kfmanager.config.AppConfig appConfig; private com.kfmanager.config.AppConfig appConfig;
private Runnable onDirectoryChangedAll;
public FilePanel(String initialPath) { public FilePanel(String initialPath) {
initComponents(); initComponents();
addNewTab(initialPath); addNewTab(initialPath);
} }
public void setOnDirectoryChangedAll(Runnable callback) {
this.onDirectoryChangedAll = callback;
}
/** Start inline rename on the currently selected tab/table. */ /** Start inline rename on the currently selected tab/table. */
public void startInlineRename() { public void startInlineRename() {
FilePanelTab tab = getCurrentTab(); FilePanelTab tab = getCurrentTab();
@ -217,7 +222,10 @@ public class FilePanel extends JPanel {
if (appConfig != null) tab.setAppConfig(appConfig); if (appConfig != null) tab.setAppConfig(appConfig);
// Set callback for updating tab title on directory change // Set callback for updating tab title on directory change
tab.setOnDirectoryChanged(() -> updateTabTitle(tab)); tab.setOnDirectoryChanged(() -> {
updateTabTitle(tab);
if (onDirectoryChangedAll != null) onDirectoryChangedAll.run();
});
// Forward switchPanel callback to the tab so TAB works from any tab // Forward switchPanel callback to the tab so TAB works from any tab
tab.setOnSwitchPanelRequested(switchPanelCallback); tab.setOnSwitchPanelRequested(switchPanelCallback);
@ -253,7 +261,10 @@ public class FilePanel extends JPanel {
public void addNewTabWithMode(String path, ViewMode mode) { public void addNewTabWithMode(String path, ViewMode mode) {
FilePanelTab tab = new FilePanelTab(path); FilePanelTab tab = new FilePanelTab(path);
if (appConfig != null) tab.setAppConfig(appConfig); if (appConfig != null) tab.setAppConfig(appConfig);
tab.setOnDirectoryChanged(() -> updateTabTitle(tab)); tab.setOnDirectoryChanged(() -> {
updateTabTitle(tab);
if (onDirectoryChangedAll != null) onDirectoryChangedAll.run();
});
tab.setOnSwitchPanelRequested(switchPanelCallback); tab.setOnSwitchPanelRequested(switchPanelCallback);
if (mode != null) { if (mode != null) {
@ -352,15 +363,60 @@ public class FilePanel extends JPanel {
private void populateDrives() { private void populateDrives() {
driveCombo.removeAllItems(); driveCombo.removeAllItems();
java.util.Set<File> driveSet = new java.util.LinkedHashSet<>();
// Add standard roots
File[] roots = File.listRoots(); File[] roots = File.listRoots();
if (roots != null) { if (roots != null) {
for (File r : roots) { for (File r : roots) {
driveSet.add(r);
}
}
// Add home directory
driveSet.add(new File(System.getProperty("user.home")));
// On Linux/Unix, add common mount points
String os = System.getProperty("os.name").toLowerCase();
if (!os.contains("win")) {
// Common Linux/Unix mount points
String user = System.getProperty("user.name");
// Typical locations for removable media
String[] commonMountPoints = {
"/media/" + user,
"/run/media/" + user,
"/mnt",
"/Volumes" // macOS
};
for (String path : commonMountPoints) {
File dir = new File(path);
if (dir.exists() && dir.isDirectory()) {
File[] subDirs = dir.listFiles();
if (subDirs != null) {
for (File sub : subDirs) {
if (sub.isDirectory()) {
driveSet.add(sub);
}
}
} else if (!path.contains(user)) {
// For /mnt we might also add the directory itself if it's empty but meant to be used
driveSet.add(dir);
}
}
}
}
for (File d : driveSet) {
try { try {
driveCombo.addItem(r); driveCombo.addItem(d);
} catch (Exception ignore) {} } catch (Exception ignore) {}
} }
// select first drive by default
if (roots.length > 0) driveCombo.setSelectedItem(roots[0]); // Initialize selection
if (driveCombo.getItemCount() > 0) {
driveCombo.setSelectedIndex(0);
} }
// Update info for currently selected drive // Update info for currently selected drive
@ -548,6 +604,11 @@ public class FilePanel extends JPanel {
return tab != null ? tab.getCurrentDirectory() : null; return tab != null ? tab.getCurrentDirectory() : null;
} }
public String getCurrentPath() {
File dir = getCurrentDirectory();
return dir != null ? dir.getAbsolutePath() : "";
}
public List<FileItem> getSelectedItems() { public List<FileItem> getSelectedItems() {
FilePanelTab tab = getCurrentTab(); FilePanelTab tab = getCurrentTab();
return tab != null ? tab.getSelectedItems() : java.util.Collections.emptyList(); return tab != null ? tab.getSelectedItems() : java.util.Collections.emptyList();

View File

@ -1149,6 +1149,14 @@ public class FilePanelTab extends JPanel {
}); });
menu.add(copyPath); menu.add(copyPath);
// Associate with...
JMenuItem associateItem = new JMenuItem("Associate with...");
associateItem.addActionListener(ae -> {
if (item.isDirectory() || item.getName().equals("..")) return;
showAssociationDialog(item.getFile());
});
menu.add(associateItem);
// Delete // Delete
JMenuItem deleteItem = new JMenuItem("Delete"); JMenuItem deleteItem = new JMenuItem("Delete");
deleteItem.addActionListener(ae -> { deleteItem.addActionListener(ae -> {
@ -2259,4 +2267,44 @@ public class FilePanelTab extends JPanel {
return 16; return 16;
} }
} }
private void showAssociationDialog(File file) {
String name = file.getName();
String ext = "";
int lastDot = name.lastIndexOf('.');
if (lastDot > 0 && lastDot < name.length() - 1) {
ext = name.substring(lastDot + 1);
} else {
ext = name; // fallback to full name if no extension
}
String pattern = JOptionPane.showInputDialog(this,
"File pattern (e.g. *.txt or txt):",
ext);
if (pattern == null || pattern.trim().isEmpty()) return;
// If user just provided an extension like "txt", convert it to "*.txt" internally for consistency
String normalizedPattern = pattern.trim();
if (!normalizedPattern.contains("*") && !normalizedPattern.contains("?")) {
normalizedPattern = "*." + normalizedPattern;
}
String existingCmd = "";
if (persistedConfig != null) {
java.util.Map<String, String> associations = persistedConfig.getFileAssociations();
existingCmd = associations.getOrDefault(normalizedPattern, "");
}
String command = JOptionPane.showInputDialog(this,
"Command to run (%f for full path, %n for name):",
existingCmd);
if (command != null && !command.trim().isEmpty()) {
if (persistedConfig != null) {
persistedConfig.setFileAssociation(normalizedPattern, command.trim());
JOptionPane.showMessageDialog(this, "Association saved for " + normalizedPattern);
}
}
}
} }

View File

@ -25,6 +25,7 @@ public class MainWindow extends JFrame {
private JPanel buttonPanel; private JPanel buttonPanel;
private JToolBar toolBar; private JToolBar toolBar;
private JComboBox<String> commandLine; private JComboBox<String> commandLine;
private JLabel cmdLabel;
private AppConfig config; private AppConfig config;
public MainWindow() { public MainWindow() {
@ -73,6 +74,7 @@ public class MainWindow extends JFrame {
leftPanel.setBorder(BorderFactory.createTitledBorder("Left panel")); leftPanel.setBorder(BorderFactory.createTitledBorder("Left panel"));
// Provide a callback so tabs inside the panel can request switching panels with TAB // Provide a callback so tabs inside the panel can request switching panels with TAB
leftPanel.setSwitchPanelCallback(() -> switchPanelsFromChild()); leftPanel.setSwitchPanelCallback(() -> switchPanelsFromChild());
leftPanel.setOnDirectoryChangedAll(() -> updateCommandLinePrompt());
// Load and set ViewMode for left panel // Load and set ViewMode for left panel
try { try {
@ -89,6 +91,7 @@ public class MainWindow extends JFrame {
rightPanel.setBorder(BorderFactory.createTitledBorder("Right panel")); rightPanel.setBorder(BorderFactory.createTitledBorder("Right panel"));
// Provide a callback so tabs inside the panel can request switching panels with TAB // Provide a callback so tabs inside the panel can request switching panels with TAB
rightPanel.setSwitchPanelCallback(() -> switchPanelsFromChild()); rightPanel.setSwitchPanelCallback(() -> switchPanelsFromChild());
rightPanel.setOnDirectoryChangedAll(() -> updateCommandLinePrompt());
// Load and set ViewMode for right panel // Load and set ViewMode for right panel
try { try {
@ -106,6 +109,7 @@ public class MainWindow extends JFrame {
// Set left panel as active by default // Set left panel as active by default
activePanel = leftPanel; activePanel = leftPanel;
updateActivePanelBorder(); updateActivePanelBorder();
updateCommandLinePrompt();
// Restore saved tabs for both panels if present in configuration // Restore saved tabs for both panels if present in configuration
try { try {
@ -153,11 +157,13 @@ public class MainWindow extends JFrame {
updateActivePanelBorder(); updateActivePanelBorder();
leftPanel.getFileTable().repaint(); leftPanel.getFileTable().repaint();
rightPanel.getFileTable().repaint(); rightPanel.getFileTable().repaint();
updateCommandLinePrompt();
} else if (SwingUtilities.isDescendingFrom(focused, rightPanel)) { } else if (SwingUtilities.isDescendingFrom(focused, rightPanel)) {
activePanel = rightPanel; activePanel = rightPanel;
updateActivePanelBorder(); updateActivePanelBorder();
leftPanel.getFileTable().repaint(); leftPanel.getFileTable().repaint();
rightPanel.getFileTable().repaint(); rightPanel.getFileTable().repaint();
updateCommandLinePrompt();
} }
} }
}); });
@ -226,7 +232,7 @@ public class MainWindow extends JFrame {
JPanel cmdPanel = new JPanel(new BorderLayout(5, 0)); JPanel cmdPanel = new JPanel(new BorderLayout(5, 0));
cmdPanel.setBorder(BorderFactory.createEmptyBorder(2, 5, 0, 5)); cmdPanel.setBorder(BorderFactory.createEmptyBorder(2, 5, 0, 5));
JLabel cmdLabel = new JLabel(System.getProperty("user.name") + ">"); cmdLabel = new JLabel(System.getProperty("user.name") + ":" + (activePanel != null ? activePanel.getCurrentDirectory().getAbsolutePath() : "") + ">");
cmdLabel.setFont(new Font("Monospaced", Font.BOLD, 12)); cmdLabel.setFont(new Font("Monospaced", Font.BOLD, 12));
cmdPanel.add(cmdLabel, BorderLayout.WEST); cmdPanel.add(cmdLabel, BorderLayout.WEST);
@ -252,6 +258,9 @@ public class MainWindow extends JFrame {
} else if (e.getKeyCode() == KeyEvent.VK_TAB) { } else if (e.getKeyCode() == KeyEvent.VK_TAB) {
activePanel.getFileTable().requestFocusInWindow(); activePanel.getFileTable().requestFocusInWindow();
e.consume(); e.consume();
} else if (e.getKeyCode() == KeyEvent.VK_E && e.isControlDown()) {
showCommandLineHistory();
e.consume();
} }
} }
}); });
@ -896,6 +905,7 @@ public class MainWindow extends JFrame {
JTable rt = rightPanel.getFileTable(); JTable rt = rightPanel.getFileTable();
if (lt != null) lt.repaint(); if (lt != null) lt.repaint();
if (rt != null) rt.repaint(); if (rt != null) rt.repaint();
updateCommandLinePrompt();
} }
/** /**
@ -905,6 +915,16 @@ public class MainWindow extends JFrame {
switchPanels(); switchPanels();
} }
private void updateCommandLinePrompt() {
if (cmdLabel == null) return;
FilePanel active = activePanel;
if (active == null) return;
String path = active.getCurrentPath();
cmdLabel.setText(path + ">");
}
/** /**
* Attach TAB handling to switch panels * Attach TAB handling to switch panels
*/ */
@ -1430,9 +1450,18 @@ public class MainWindow extends JFrame {
* Show history of command line * Show history of command line
*/ */
private void showCommandLineHistory() { private void showCommandLineHistory() {
if (commandLine != null) { if (commandLine != null && commandLine.getItemCount() > 0) {
commandLine.requestFocusInWindow(); commandLine.requestFocusInWindow();
if (!commandLine.isPopupVisible()) {
commandLine.setSelectedIndex(0);
commandLine.showPopup(); commandLine.showPopup();
} else {
int count = commandLine.getItemCount();
int current = commandLine.getSelectedIndex();
// If index is -1 or invalid, start from 0, otherwise go to next
int nextIndex = (current < 0) ? 0 : (current + 1) % count;
commandLine.setSelectedIndex(nextIndex);
}
} }
} }
@ -1526,58 +1555,13 @@ public class MainWindow extends JFrame {
// Add to history // Add to history
addCommandToHistory(command.trim()); addCommandToHistory(command.trim());
File currentDir = activePanel.getCurrentDirectory(); // Final prompt update after command execution (path might have changed)
if (currentDir == null) { updateCommandLinePrompt();
currentDir = new File(System.getProperty("user.home"));
}
try { // Execute natively, not via bash wrapper
String osName = System.getProperty("os.name").toLowerCase(); executeNative(command.trim(), null);
ProcessBuilder pb;
if (osName.contains("win")) { // Clear after execution and return focus
// Windows: cmd /c command && pause
pb = new ProcessBuilder("cmd.exe", "/c", "start", "cmd.exe", "/k", command);
} else if (osName.contains("mac")) {
// macOS: Open terminal and execute
String appleScript = String.format(
"tell application \"Terminal\" to do script \"cd '%s' && %s\"",
currentDir.getAbsolutePath(), command.replace("\"", "\\\"")
);
pb = new ProcessBuilder("osascript", "-e", appleScript);
} else {
// Linux: Try common terminals with -e or --command
String[] terminals = {"gnome-terminal", "konsole", "xfce4-terminal", "mate-terminal", "xterm"};
pb = null;
for (String terminal : terminals) {
try {
Process p = Runtime.getRuntime().exec(new String[]{"which", terminal});
if (p.waitFor() == 0) {
if (terminal.equals("gnome-terminal") || terminal.equals("xfce4-terminal") || terminal.equals("mate-terminal")) {
pb = new ProcessBuilder(terminal, "--working-directory=" + currentDir.getAbsolutePath(), "--", "bash", "-c", command + "; echo; echo 'Press Enter to close...'; read");
} else if (terminal.equals("konsole")) {
pb = new ProcessBuilder(terminal, "--workdir", currentDir.getAbsolutePath(), "-e", "bash", "-c", command + "; echo; echo 'Press Enter to close...'; read");
} else {
pb = new ProcessBuilder(terminal, "-e", "bash -c \"" + command + "; echo; echo 'Press Enter to close...'; read\"");
}
break;
}
} catch (Exception e) {
// try next
}
}
if (pb == null) {
pb = new ProcessBuilder("xterm", "-e", "bash -c \"" + command + "; echo; echo 'Press Enter to close...'; read\"");
}
}
if (pb != null) {
pb.directory(currentDir);
pb.start();
// Clear after execution
Component editorComp = commandLine.getEditor().getEditorComponent(); Component editorComp = commandLine.getEditor().getEditorComponent();
if (editorComp instanceof JTextField) { if (editorComp instanceof JTextField) {
((JTextField) editorComp).setText(""); ((JTextField) editorComp).setText("");
@ -1587,14 +1571,6 @@ public class MainWindow extends JFrame {
activePanel.getFileTable().requestFocusInWindow(); activePanel.getFileTable().requestFocusInWindow();
} }
} catch (Exception e) {
JOptionPane.showMessageDialog(this,
"Error executing command: " + e.getMessage(),
"Error",
JOptionPane.ERROR_MESSAGE);
}
}
private void executeNative(String command, String workingDir) { private void executeNative(String command, String workingDir) {
if (command == null || command.trim().isEmpty()) return; if (command == null || command.trim().isEmpty()) return;