new system for associations

This commit is contained in:
rdavidek 2026-01-21 23:23:59 +01:00
parent 5aeba21e9a
commit a4afc103ad
3 changed files with 88 additions and 144 deletions

View File

@ -367,42 +367,57 @@ public class AppConfig {
}
}
// --- File Associations ---
/** Returns a map of pattern (extension or wildcard) -> command */
public java.util.Map<String, String> getFileAssociations() {
java.util.Map<String, String> map = new java.util.HashMap<>();
for (String key : properties.stringPropertyNames()) {
if (key.startsWith("assoc.")) {
String pattern = key.substring(6);
map.put(pattern, properties.getProperty(key));
}
}
return map;
}
// --- Open With Configuration ---
public static class OpenWithEntry {
public String label;
public String type; // "directory" or extension
public String command;
public void setFileAssociations(java.util.Map<String, String> associations) {
// Remove all current associations first
for (String key : properties.stringPropertyNames()) {
if (key.startsWith("assoc.")) {
properties.remove(key);
}
}
// Add new ones
if (associations != null) {
for (java.util.Map.Entry<String, String> entry : associations.entrySet()) {
String pattern = entry.getKey().trim();
if (!pattern.isEmpty()) {
properties.setProperty("assoc." + pattern, entry.getValue());
}
}
public OpenWithEntry() {}
public OpenWithEntry(String label, String type, String command) {
this.label = label;
this.type = type;
this.command = command;
}
}
/** 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();
public java.util.List<OpenWithEntry> getOpenWithEntries() {
java.util.List<OpenWithEntry> list = new java.util.ArrayList<>();
String countStr = properties.getProperty("openwith.count", "0");
int count = Integer.parseInt(countStr);
for (int i = 0; i < count; i++) {
String label = properties.getProperty("openwith." + i + ".label");
String type = properties.getProperty("openwith." + i + ".type");
String command = properties.getProperty("openwith." + i + ".command");
if (label != null && type != null && command != null) {
list.add(new OpenWithEntry(label, type, command));
}
}
return list;
}
public void setOpenWithEntries(java.util.List<OpenWithEntry> entries) {
// Remove old entries
String countStr = properties.getProperty("openwith.count", "0");
int count = Integer.parseInt(countStr);
for (int i = 0; i < count; i++) {
properties.remove("openwith." + i + ".label");
properties.remove("openwith." + i + ".type");
properties.remove("openwith." + i + ".command");
}
if (entries == null) {
properties.setProperty("openwith.count", "0");
return;
}
properties.setProperty("openwith.count", String.valueOf(entries.size()));
for (int i = 0; i < entries.size(); i++) {
OpenWithEntry e = entries.get(i);
properties.setProperty("openwith." + i + ".label", e.label != null ? e.label : "");
properties.setProperty("openwith." + i + ".type", e.type != null ? e.type : "");
properties.setProperty("openwith." + i + ".command", e.command != null ? e.command : "");
}
}
// --- Appearance (global) settings ---

View File

@ -1346,48 +1346,22 @@ public class FilePanelTab extends JPanel {
private void openFileNative(File file) {
try {
// 1. Check custom associations from config (wildcard matching)
// 1. Check custom Open with entries from config
if (persistedConfig != null) {
java.util.Map<String, String> associations = persistedConfig.getFileAssociations();
String name = file.getName();
java.util.List<cz.kamma.kfmanager.config.AppConfig.OpenWithEntry> owEntries = persistedConfig.getOpenWithEntries();
String name = file.getName().toLowerCase();
String ext = "";
int dot = name.lastIndexOf('.');
if (dot > 0 && dot < name.length() - 1) {
ext = name.substring(dot + 1);
}
String command = null;
// Sort associations by length descending to match more specific patterns first (e.g. *.tar.gz before *.gz)
java.util.List<String> patterns = new java.util.ArrayList<>(associations.keySet());
patterns.sort((a, b) -> Integer.compare(b.length(), a.length()));
for (String pattern : patterns) {
String fullCommand = associations.get(pattern);
// Support multiple patterns separated by semicolon (e.g. "jpg;png;gif")
String[] subPatterns = pattern.split(";");
boolean matched = false;
for (String sub : subPatterns) {
String glob = sub.trim();
if (glob.isEmpty()) continue;
// If user just provided an extension like "txt", convert to "*.txt"
if (!glob.contains("*") && !glob.contains("?")) {
glob = "*." + glob;
for (cz.kamma.kfmanager.config.AppConfig.OpenWithEntry entry : owEntries) {
if (entry.type != null && entry.type.equalsIgnoreCase(ext)) {
command = entry.command;
break; // Use the first matching entry
}
try {
java.nio.file.PathMatcher matcher = java.nio.file.FileSystems.getDefault().getPathMatcher("glob:" + glob);
if (matcher.matches(java.nio.file.Paths.get(name))) {
command = fullCommand;
matched = true;
break;
}
} catch (Exception ignore) {
// Fallback for simple extension match if glob is invalid
if (name.toLowerCase().endsWith("." + glob.toLowerCase())) {
command = fullCommand;
matched = true;
break;
}
}
}
if (matched) break;
}
if (command != null && !command.trim().isEmpty()) {
@ -1612,14 +1586,6 @@ public class FilePanelTab extends JPanel {
menu.addSeparator();
// 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
JMenuItem deleteItem = new JMenuItem("Delete");
deleteItem.addActionListener(ae -> {
@ -3026,44 +2992,4 @@ public class FilePanelTab extends JPanel {
return null;
}
}
private void showAssociationDialog(File file) {
String name = file.getName();
String initialPattern = "";
int lastDot = name.lastIndexOf('.');
if (lastDot > 0 && lastDot < name.length() - 1) {
initialPattern = "*." + name.substring(lastDot + 1).toLowerCase();
} else {
initialPattern = name; // fallback to full name if no extension
}
String pattern = JOptionPane.showInputDialog(this,
"File pattern (e.g. *.txt or txt):",
initialPattern);
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

@ -30,7 +30,7 @@ public class SettingsDialog extends JDialog {
private final String originalExternalEditorPath;
private final int originalToolbarButtonSize;
private final int originalToolbarIconSize;
private final java.util.Map<String, String> originalFileAssociations;
private final java.util.List<cz.kamma.kfmanager.config.AppConfig.OpenWithEntry> originalOpenWithEntries;
// Appearance controls
private JButton appearanceFontBtn;
@ -60,7 +60,7 @@ public class SettingsDialog extends JDialog {
this.originalExternalEditorPath = config.getExternalEditorPath();
this.originalToolbarButtonSize = config.getToolbarButtonSize();
this.originalToolbarIconSize = config.getToolbarIconSize();
this.originalFileAssociations = new java.util.HashMap<>(config.getFileAssociations());
this.originalOpenWithEntries = new java.util.ArrayList<>(config.getOpenWithEntries());
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setSize(700, 420);
@ -73,7 +73,7 @@ public class SettingsDialog extends JDialog {
model.addElement("Behavior");
model.addElement("Sorting");
model.addElement("Toolbar");
model.addElement("Associations");
model.addElement("Open with");
model.addElement("Import/Export");
categoryList = new JList<>(model);
categoryList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
@ -88,7 +88,7 @@ public class SettingsDialog extends JDialog {
cards.add(buildBehaviorPanel(), "Behavior");
cards.add(buildSortingPanel(), "Sorting");
cards.add(buildToolbarPanel(), "Toolbar");
cards.add(buildAssociationsPanel(), "Associations");
cards.add(buildOpenWithPanel(), "Open with");
cards.add(buildImportExportPanel(), "Import/Export");
categoryList.addListSelectionListener(e -> {
@ -181,21 +181,24 @@ public class SettingsDialog extends JDialog {
} catch (Exception ignore) {}
}
// Collect Associations settings
JPanel assocHolder = (JPanel) panels.get("Associations");
if (assocHolder != null) {
// Collect Open with settings
JPanel owHolder = (JPanel) panels.get("Open with");
if (owHolder != null) {
try {
DefaultTableModel assocModel = (DefaultTableModel) assocHolder.getClientProperty("tableModel");
if (assocModel != null) {
java.util.Map<String, String> map = new java.util.HashMap<>();
for (int i = 0; i < assocModel.getRowCount(); i++) {
String ext = (String) assocModel.getValueAt(i, 0);
String cmd = (String) assocModel.getValueAt(i, 1);
if (ext != null && !ext.trim().isEmpty() && cmd != null && !cmd.trim().isEmpty()) {
map.put(ext.trim(), cmd.trim());
DefaultTableModel owModel = (DefaultTableModel) owHolder.getClientProperty("tableModel");
if (owModel != null) {
java.util.List<cz.kamma.kfmanager.config.AppConfig.OpenWithEntry> list = new java.util.ArrayList<>();
for (int i = 0; i < owModel.getRowCount(); i++) {
String label = (String) owModel.getValueAt(i, 0);
String type = (String) owModel.getValueAt(i, 1);
String cmd = (String) owModel.getValueAt(i, 2);
if (label != null && !label.trim().isEmpty() &&
type != null && !type.trim().isEmpty() &&
cmd != null && !cmd.trim().isEmpty()) {
list.add(new cz.kamma.kfmanager.config.AppConfig.OpenWithEntry(label.trim(), type.trim(), cmd.trim()));
}
}
config.setFileAssociations(map);
config.setOpenWithEntries(list);
}
} catch (Exception ignore) {}
}
@ -231,7 +234,7 @@ public class SettingsDialog extends JDialog {
config.setExternalEditorPath(originalExternalEditorPath);
config.setToolbarButtonSize(originalToolbarButtonSize);
config.setToolbarIconSize(originalToolbarIconSize);
config.setFileAssociations(originalFileAssociations);
config.setOpenWithEntries(originalOpenWithEntries);
// Notify UI to revert changes
if (onChange != null) onChange.run();
@ -632,19 +635,19 @@ public class SettingsDialog extends JDialog {
return p;
}
private JPanel buildAssociationsPanel() {
private JPanel buildOpenWithPanel() {
JPanel p = new JPanel(new BorderLayout(8, 8));
p.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
JLabel hint = new JLabel("<html>Use <b>wildcards</b> (e.g. *.tar.gz) or plain extension (e.g. txt).<br>" +
JLabel hint = new JLabel("<html>Use <b>directory</b> or <b>extension</b> (e.g. txt) for Type.<br>" +
"Use <b>%f</b> for full path, <b>%n</b> for filename. " +
"If no placeholder is used, path is appended to the end.</html>");
p.add(hint, BorderLayout.NORTH);
DefaultTableModel model = new DefaultTableModel(new String[]{"Pattern", "Command"}, 0);
java.util.Map<String, String> current = config.getFileAssociations();
for (java.util.Map.Entry<String, String> entry : current.entrySet()) {
model.addRow(new Object[]{entry.getKey(), entry.getValue()});
DefaultTableModel model = new DefaultTableModel(new String[]{"Label", "Type", "Command"}, 0);
java.util.List<cz.kamma.kfmanager.config.AppConfig.OpenWithEntry> current = config.getOpenWithEntries();
for (cz.kamma.kfmanager.config.AppConfig.OpenWithEntry entry : current) {
model.addRow(new Object[]{entry.label, entry.type, entry.command});
}
JTable table = new JTable(model);
@ -652,7 +655,7 @@ public class SettingsDialog extends JDialog {
JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
JButton addBtn = new JButton("Add");
addBtn.addActionListener(e -> model.addRow(new Object[]{"", ""}));
addBtn.addActionListener(e -> model.addRow(new Object[]{"", "", ""}));
JButton removeBtn = new JButton("Remove");
removeBtn.addActionListener(e -> {
int row = table.getSelectedRow();
@ -665,7 +668,7 @@ public class SettingsDialog extends JDialog {
// Save reference for OK action
JPanel holder = new JPanel();
holder.putClientProperty("tableModel", model);
panels.put("Associations", holder);
panels.put("Open with", holder);
return p;
}