toolbar shortcuts added

This commit is contained in:
Radek Davidek 2026-01-16 16:19:08 +01:00
parent 2c32493e73
commit f63636eae3
3 changed files with 398 additions and 22 deletions

View File

@ -561,4 +561,54 @@ public class AppConfig {
properties.remove("cmd.history." + i);
}
}
// --- Toolbar shortcuts persistence ---
public static class ToolbarShortcut implements Serializable {
public String command;
public String label;
public String iconPath;
public String workingDir;
public ToolbarShortcut(String command, String label, String iconPath, String workingDir) {
this.command = command;
this.label = label;
this.iconPath = iconPath;
this.workingDir = workingDir;
}
}
public java.util.List<ToolbarShortcut> getToolbarShortcuts() {
java.util.List<ToolbarShortcut> list = new java.util.ArrayList<>();
int count = Integer.parseInt(properties.getProperty("toolbar.shortcuts.count", "0"));
for (int i = 0; i < count; i++) {
String cmd = properties.getProperty("toolbar.shortcut." + i + ".command");
String label = properties.getProperty("toolbar.shortcut." + i + ".label");
String icon = properties.getProperty("toolbar.shortcut." + i + ".iconPath");
String workDir = properties.getProperty("toolbar.shortcut." + i + ".workingDir");
if (cmd != null) {
list.add(new ToolbarShortcut(cmd, label, icon, workDir));
}
}
return list;
}
public void saveToolbarShortcuts(java.util.List<ToolbarShortcut> shortcuts) {
// remove old entries first
int old = Integer.parseInt(properties.getProperty("toolbar.shortcuts.count", "0"));
for (int i = 0; i < old; i++) {
properties.remove("toolbar.shortcut." + i + ".command");
properties.remove("toolbar.shortcut." + i + ".label");
properties.remove("toolbar.shortcut." + i + ".iconPath");
properties.remove("toolbar.shortcut." + i + ".workingDir");
}
properties.setProperty("toolbar.shortcuts.count", String.valueOf(shortcuts.size()));
for (int i = 0; i < shortcuts.size(); i++) {
ToolbarShortcut s = shortcuts.get(i);
properties.setProperty("toolbar.shortcut." + i + ".command", s.command != null ? s.command : "");
properties.setProperty("toolbar.shortcut." + i + ".label", s.label != null ? s.label : "");
properties.setProperty("toolbar.shortcut." + i + ".iconPath", s.iconPath != null ? s.iconPath : "");
properties.setProperty("toolbar.shortcut." + i + ".workingDir", s.workingDir != null ? s.workingDir : "");
}
}
}

View File

@ -6,7 +6,10 @@ import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@ -275,10 +278,32 @@ public class FilePanelTab extends JPanel {
return;
}
// Ignore mouse pressed/released/dragged events to prevent selection changes
if (e.getID() == java.awt.event.MouseEvent.MOUSE_PRESSED ||
e.getID() == java.awt.event.MouseEvent.MOUSE_RELEASED ||
e.getID() == java.awt.event.MouseEvent.MOUSE_DRAGGED) {
// Allow MOUSE_PRESSED for drag initiating gestures, but block standard selection change.
// We'll process selection manually in MOUSE_CLICKED above.
if (e.getID() == java.awt.event.MouseEvent.MOUSE_PRESSED) {
// Start selection logic on press to support DnD initiate
int col = columnAtPoint(e.getPoint());
int row = rowAtPoint(e.getPoint());
if (row >= 0) {
if (viewMode == ViewMode.BRIEF) {
FileItem item = tableModel.getItemFromBriefLayout(row, col);
if (item != null) {
int index = tableModel.items.indexOf(item);
if (index >= 0) {
int selRow = index % tableModel.briefRowsPerColumn;
fileTable.setRowSelectionInterval(selRow, selRow);
briefCurrentColumn = index / tableModel.briefRowsPerColumn;
}
}
} else {
fileTable.setRowSelectionInterval(row, row);
}
fileTable.requestFocusInWindow();
repaint();
}
}
if (e.getID() == java.awt.event.MouseEvent.MOUSE_RELEASED) {
e.consume();
return;
}
@ -287,20 +312,60 @@ public class FilePanelTab extends JPanel {
}
@Override
protected void processMouseMotionEvent(java.awt.event.MouseEvent e) {
// Block mouse-dragged events so dragging cannot change selection
// or initiate any drag behavior. Consume the event and do nothing.
if (e.getID() == java.awt.event.MouseEvent.MOUSE_DRAGGED) {
e.consume();
return;
}
// Allow mouse movement to pass through to support Drag and Drop
super.processMouseMotionEvent(e);
}
};
// Disable Swing drag-and-drop support and transfer handling on the table
// to ensure no drag gestures or DnD occur.
fileTable.setDragEnabled(false);
fileTable.setTransferHandler(null);
// Enable Drag and Drop on the table
fileTable.setDragEnabled(true);
fileTable.setTransferHandler(new TransferHandler() {
@Override
public int getSourceActions(JComponent c) {
return COPY;
}
@Override
protected Transferable createTransferable(JComponent c) {
List<FileItem> selected = getSelectedItems();
if (selected.isEmpty()) {
// If nothing explicitly marked, use the focused item
FileItem focused = getFocusedItem();
if (focused != null && !focused.getName().equals("..")) {
selected = new ArrayList<>();
selected.add(focused);
}
}
final List<File> files = new ArrayList<>();
for (FileItem item : selected) {
if (!item.getName().equals("..")) {
files.add(item.getFile());
}
}
if (files.isEmpty()) return null;
return new Transferable() {
@Override
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[]{DataFlavor.javaFileListFlavor};
}
@Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return DataFlavor.javaFileListFlavor.equals(flavor);
}
@Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
if (!isDataFlavorSupported(flavor)) throw new UnsupportedFlavorException(flavor);
return files;
}
};
}
});
fileTable.setFocusTraversalKeysEnabled(false);
try {
fileTable.setDropMode(null);

View File

@ -7,8 +7,11 @@ import com.kfmanager.service.FileOperations;
import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
@ -20,6 +23,7 @@ public class MainWindow extends JFrame {
private FilePanel rightPanel;
private FilePanel activePanel;
private JPanel buttonPanel;
private JToolBar toolBar;
private JComboBox<String> commandLine;
private AppConfig config;
@ -270,9 +274,37 @@ public class MainWindow extends JFrame {
* Create toolbar with buttons for changing view mode
*/
private void createToolBar() {
JToolBar toolBar = new JToolBar();
toolBar.setFloatable(false);
toolBar.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
if (toolBar == null) {
toolBar = new JToolBar();
toolBar.setFloatable(false);
toolBar.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
add(toolBar, BorderLayout.NORTH);
// Enable Drag and Drop for adding shortcuts
toolBar.setTransferHandler(new TransferHandler() {
@Override
public boolean canImport(TransferSupport support) {
return support.isDataFlavorSupported(DataFlavor.javaFileListFlavor);
}
@Override
@SuppressWarnings("unchecked")
public boolean importData(TransferSupport support) {
if (!canImport(support)) return false;
try {
List<File> files = (List<File>) support.getTransferable().getTransferData(DataFlavor.javaFileListFlavor);
if (files != null && !files.isEmpty()) {
showAddToolbarShortcutDialog(files.get(0));
}
return true;
} catch (Exception e) {
return false;
}
}
});
}
toolBar.removeAll();
// Button for BRIEF mode
JButton btnBrief = new JButton("☰ Brief");
@ -299,12 +331,165 @@ public class MainWindow extends JFrame {
toolBar.addSeparator();
// Info label
JLabel infoLabel = new JLabel(" View Mode ");
infoLabel.setFont(infoLabel.getFont().deriveFont(Font.PLAIN, 10f));
toolBar.add(infoLabel);
// Load custom shortcuts from config
List<AppConfig.ToolbarShortcut> shortcuts = config.getToolbarShortcuts();
for (AppConfig.ToolbarShortcut s : shortcuts) {
JButton btn = new JButton();
boolean hasIcon = false;
if (s.iconPath != null && !s.iconPath.isEmpty()) {
try {
File iconFile = new File(s.iconPath);
if (iconFile.exists()) {
btn.setIcon(new ImageIcon(new ImageIcon(s.iconPath).getImage().getScaledInstance(16, 16, Image.SCALE_SMOOTH)));
hasIcon = true;
}
} catch (Exception ignore) {}
}
add(toolBar, BorderLayout.NORTH);
// If no icon found, use the label text, otherwise use label as tooltip
if (!hasIcon) {
btn.setText(s.label);
}
btn.setToolTipText(s.label + " (" + s.command + ")");
btn.setFocusable(false);
btn.addActionListener(e -> executeNative(s.command, s.workingDir));
// Context menu for Edit/Delete
JPopupMenu shortcutPopup = new JPopupMenu();
JMenuItem editItem = new JMenuItem("Edit");
editItem.addActionListener(ae -> showEditToolbarShortcutDialog(s));
JMenuItem deleteItem = new JMenuItem("Delete");
deleteItem.addActionListener(ae -> {
int choice = JOptionPane.showConfirmDialog(MainWindow.this, "Remove this shortcut?", "Toolbar", JOptionPane.YES_NO_OPTION);
if (choice == JOptionPane.YES_OPTION) {
removeToolbarShortcut(s);
}
});
shortcutPopup.add(editItem);
shortcutPopup.add(deleteItem);
btn.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (e.isPopupTrigger()) {
shortcutPopup.show(e.getComponent(), e.getX(), e.getY());
}
}
@Override
public void mouseReleased(MouseEvent e) {
if (e.isPopupTrigger()) {
shortcutPopup.show(e.getComponent(), e.getX(), e.getY());
}
}
});
toolBar.add(btn);
}
toolBar.revalidate();
toolBar.repaint();
}
private void showAddToolbarShortcutDialog(File file) {
showToolbarShortcutEditor(null, file);
}
private void showEditToolbarShortcutDialog(AppConfig.ToolbarShortcut shortcut) {
showToolbarShortcutEditor(shortcut, null);
}
private void showToolbarShortcutEditor(AppConfig.ToolbarShortcut existing, File file) {
String initialLabel = (existing != null) ? existing.label : (file != null ? file.getName() : "");
String initialCmd = (existing != null) ? existing.command : (file != null ? file.getAbsolutePath() : "");
String initialWorkDir = (existing != null) ? existing.workingDir :
(file != null ? (file.isDirectory() ? file.getAbsolutePath() : file.getParent()) : "");
String initialIcon = (existing != null) ? existing.iconPath : "";
JTextField labelField = new JTextField(initialLabel);
JTextField cmdField = new JTextField(initialCmd);
JTextField workingDirField = new JTextField(initialWorkDir);
JTextField iconField = new JTextField(initialIcon);
JPanel panel = new JPanel(new GridLayout(0, 1));
panel.add(new JLabel("Label:"));
panel.add(labelField);
panel.add(new JLabel("Command:"));
panel.add(cmdField);
panel.add(new JLabel("Working directory:"));
JPanel workDirPanel = new JPanel(new BorderLayout());
workDirPanel.add(workingDirField, BorderLayout.CENTER);
JButton browseWorkDir = new JButton("...");
browseWorkDir.addActionListener(e -> {
JFileChooser chooser = new JFileChooser(workingDirField.getText());
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
workingDirField.setText(chooser.getSelectedFile().getAbsolutePath());
}
});
workDirPanel.add(browseWorkDir, BorderLayout.EAST);
panel.add(workDirPanel);
panel.add(new JLabel("Icon path (optional):"));
JPanel iconPanel = new JPanel(new BorderLayout());
iconPanel.add(iconField, BorderLayout.CENTER);
JButton browseIcon = new JButton("...");
browseIcon.addActionListener(e -> {
String startPath = iconField.getText().trim();
if (startPath.isEmpty()) {
startPath = workingDirField.getText().trim();
if (startPath.isEmpty()) {
startPath = cmdField.getText().trim();
}
}
JFileChooser chooser = new JFileChooser(startPath);
if (chooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
iconField.setText(chooser.getSelectedFile().getAbsolutePath());
}
});
iconPanel.add(browseIcon, BorderLayout.EAST);
panel.add(iconPanel);
String title = (existing != null) ? "Edit Toolbar Shortcut" : "Add Toolbar Shortcut";
int result = JOptionPane.showConfirmDialog(this, panel, title, JOptionPane.OK_CANCEL_OPTION);
if (result == JOptionPane.OK_OPTION) {
String label = labelField.getText().trim();
String command = cmdField.getText().trim();
String icon = iconField.getText().trim();
String workDir = workingDirField.getText().trim();
if (!command.isEmpty()) {
if (label.isEmpty()) label = (file != null ? file.getName() : "Shortcut");
List<AppConfig.ToolbarShortcut> shortcuts = config.getToolbarShortcuts();
if (existing != null) {
// Find and replace
for (int i = 0; i < shortcuts.size(); i++) {
AppConfig.ToolbarShortcut s = shortcuts.get(i);
// Identify by original command/label if they haven't changed yet in memory
if (s.command.equals(existing.command) && s.label.equals(existing.label)) {
shortcuts.set(i, new AppConfig.ToolbarShortcut(command, label, icon, workDir));
break;
}
}
} else {
shortcuts.add(new AppConfig.ToolbarShortcut(command, label, icon, workDir));
}
config.saveToolbarShortcuts(shortcuts);
createToolBar(); // refresh
}
}
}
private void removeToolbarShortcut(AppConfig.ToolbarShortcut shortcut) {
List<AppConfig.ToolbarShortcut> shortcuts = config.getToolbarShortcuts();
shortcuts.removeIf(s -> s.command.equals(shortcut.command) && s.label.equals(shortcut.label));
config.saveToolbarShortcuts(shortcuts);
createToolBar();
}
/**
@ -1361,6 +1546,82 @@ public class MainWindow extends JFrame {
}
}
private void executeNative(String command, String workingDir) {
if (command == null || command.trim().isEmpty()) return;
File currentDir;
if (workingDir != null && !workingDir.trim().isEmpty()) {
currentDir = new File(workingDir);
} else {
currentDir = activePanel.getCurrentDirectory();
}
if (currentDir == null || !currentDir.exists()) {
currentDir = new File(System.getProperty("user.home"));
}
try {
// Check if it's a file path that exists (and not a complex command)
if (!command.contains(" ") || (command.startsWith("\"") && command.endsWith("\"") && !command.substring(1, command.length()-1).contains("\""))) {
String path = command.startsWith("\"") ? command.substring(1, command.length()-1) : command;
File file = new File(path);
if (file.exists()) {
// Start executable files directly, otherwise use Desktop.open
if (file.canExecute() && !file.isDirectory()) {
new ProcessBuilder(file.getAbsolutePath()).directory(currentDir).start();
return;
}
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) {
Desktop.getDesktop().open(file);
return;
}
}
}
// Otherwise try running as a native process
List<String> cmdList = parseCommand(command);
try {
new ProcessBuilder(cmdList).directory(currentDir).start();
} catch (IOException ex) {
// Fallback for Linux/macOS: try via shell
String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("linux") || osName.contains("mac")) {
new ProcessBuilder("sh", "-c", command).directory(currentDir).start();
} else {
throw ex;
}
}
} catch (Exception e) {
JOptionPane.showMessageDialog(this,
"Error executing native command: " + e.getMessage(),
"Error",
JOptionPane.ERROR_MESSAGE);
}
}
private List<String> parseCommand(String command) {
List<String> list = new ArrayList<>();
StringBuilder sb = new StringBuilder();
boolean inQuotes = false;
for (int i = 0; i < command.length(); i++) {
char c = command.charAt(i);
if (c == '\"') {
inQuotes = !inQuotes;
} else if (c == ' ' && !inQuotes) {
if (sb.length() > 0) {
list.add(sb.toString());
sb.setLength(0);
}
} else {
sb.append(c);
}
}
if (sb.length() > 0) {
list.add(sb.toString());
}
return list;
}
private void addCommandToHistory(String command) {
if (command == null || command.isEmpty()) return;